update roadmap, add MCP control surface sketches, fix core services config and adapters
- Update agent-roadmap ROADMAP.md and PHASE.md - Add mcp-control-surface-sketch.md and plane-origin-authoring-roundtrip-sync.md - Update live-cycle-smoke.md test doc - Fix services/core: Dockerfile, docker-compose, config, OpenAI client, authoring request - Update config and test files for core service
This commit is contained in:
parent
b5cc76877b
commit
fc84d212d8
15 changed files with 293 additions and 103 deletions
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
NomadCode는 Flutter 기반 앱, core 서비스, 공유 계약, agent-operation 규칙을 하나의 원레포로 묶어 AI-assisted development workflow를 조율하는 프로젝트다.
|
||||
|
||||
현재 로드맵은 `ROADMAP.md -> phase/<phase-slug>/PHASE.md -> phase/<phase-slug>/milestones/<milestone-slug>.md` scaffold를 기준으로 관리한다. React/Vite 웹 콘솔 제거, 서버/Plane/provider 기반 작업, Flutter-first 클라이언트 정리, Mattermost push plugin extraction, client integration 표준화, workspace 포트/환경 표준화, External Integration, Milestone Work Item Creation Sync, Plane Work Item Webhook Intake, Gito Branch Event Creation Sync Bridge, Gito proto-socket consumer wire readiness, Gito HTTP Webhook Consumer Readiness는 완료되었다. Agent-Ops MCP Control Plane의 활성 Milestone은 Roadmap Driven Agent-Ops Automation으로, roadmap/action core와 provider sync domain의 상위 방향을 정리한다.
|
||||
현재 로드맵은 `ROADMAP.md -> phase/<phase-slug>/PHASE.md -> phase/<phase-slug>/milestones/<milestone-slug>.md` scaffold를 기준으로 관리한다. React/Vite 웹 콘솔 제거, 서버/Plane/provider 기반 작업, Flutter-first 클라이언트 정리, Mattermost push plugin extraction, client integration 표준화, workspace 포트/환경 표준화, External Integration, Milestone Work Item Creation Sync, Plane Work Item Webhook Intake, Gito Branch Event Creation Sync Bridge, Gito proto-socket consumer wire readiness, Gito HTTP Webhook Consumer Readiness는 완료되었다. Agent-Ops MCP Control Plane에서는 Roadmap Driven Agent-Ops Automation으로 roadmap/action core와 provider sync domain의 상위 방향을 정리하고, Plane-Origin Authoring Roundtrip Sync로 최신 dev 환경의 Plane -> IOP -> Gito -> Plane Todo 왕복 동기화를 다시 닫는다. 외부 agent 제어 표면은 현재 확정 범위가 아니라 후속 스케치에서 검토한다.
|
||||
|
||||
IOP 외부 실행 호출은 OpenAI-compatible Responses API 방식을 기본 계약으로 채택하고, NomadCode/IOP 고유의 task, workspace, session, approval, artifact, notification 문맥은 별도 `iop` wrapper field가 아니라 `metadata` 확장으로 전달한다. A2A는 agent-to-agent delegation이 명확히 필요할 때 재검토하며, IOP native protocol은 NomadCode의 기본 외부 실행 호출 표면으로 쓰지 않는다.
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ IOP 외부 실행 호출은 OpenAI-compatible Responses API 방식을 기본 계
|
|||
- 요약: Plane 확장, Jira-compatible provider 추상화, Mattermost, Agent Integrator, IOP OpenAI-compatible Responses 호출 경계를 실제 통합 adapter 흐름으로 확장했고, metadata 실행 문맥 전달, IOP Edge `/v1/responses`, NomadCode Core 원격 create/enqueue/poll smoke를 완료했다.
|
||||
- [진행중] Agent-Ops MCP Control Plane
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||
- 요약: 로드맵 기반 agent-ops 운영 자동화, Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인, 외부 agent용 MCP 제어 표면을 다루며, 현재 Roadmap Driven Agent-Ops Automation에서 roadmap/action core와 provider sync domain의 상위 방향을 정리한다.
|
||||
- 요약: 로드맵 기반 agent-ops 운영 자동화와 Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인을 다룬다. 현재 Roadmap Driven Agent-Ops Automation은 상위 방향을 정리하고, Plane-Origin Authoring Roundtrip Sync는 완료 처리된 creation sync를 최신 IOP/Gito/Plane dev 환경 기준으로 재검증하고 보강한다. 외부 agent 제어 표면은 후속 스케치 항목으로 보류한다.
|
||||
- [계획] Project Workspace Management UX
|
||||
- 경로: `agent-roadmap/phase/project-workspace-management-ux/PHASE.md`
|
||||
- 요약: client integration 표준화, core workflow, 외부 통합 기준 이후 프로젝트 단위 앱 UX를 다루며, provider slot 기반 외부 console composition은 계획 후보로 둔다.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## 목표
|
||||
|
||||
로드맵 기반 agent-ops 운영 루프, Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인, 외부 agent 제어 표면을 Core action과 MCP tool 계층으로 분리한다. 현재는 Gito HTTP Webhook Consumer Readiness까지 완료/archive했고, Roadmap Driven Agent-Ops Automation을 활성 Milestone으로 두어 roadmap/action core와 provider sync domain의 상위 방향을 정리한다.
|
||||
로드맵 기반 agent-ops 운영 루프와 Plane/Jira 같은 work item provider와 Milestone item의 양방향 동기화 도메인을 Core action 경계로 정리한다. 외부 agent 제어 표면은 현재 확정하지 않고 후속 스케치로 분리한다. 현재는 Gito HTTP Webhook Consumer Readiness까지 완료/archive했고, Roadmap Driven Agent-Ops Automation을 상위 설계 Milestone으로 유지한다. 최신 dev 환경에서 확인된 Plane-origin authoring 회귀는 Plane-Origin Authoring Roundtrip Sync에서 별도 구현 slice로 닫는다.
|
||||
|
||||
## Milestone 흐름
|
||||
|
||||
|
|
@ -42,15 +42,23 @@
|
|||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/roadmap-driven-agent-ops-automation.md`
|
||||
- 요약: roadmap/action core와 Plane/Jira 기반 Milestone item 동기화의 상위 방향과 계약을 정리하는 활성 설계 마일스톤이며, 실제 구현은 slice 마일스톤으로 진행한다.
|
||||
|
||||
- [계획] Plane-Origin Authoring Roundtrip Sync
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/plane-origin-authoring-roundtrip-sync.md`
|
||||
- 요약: 완료 처리된 Plane-origin creation sync를 최신 dev IOP Responses, workspace slot, Gito HTTP webhook, provider identity, Plane Todo projection 기준으로 재검증하고 보강하는 구현 마일스톤이다.
|
||||
|
||||
- [계획] Milestone Execution Lifecycle Sync
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/milestone-execution-lifecycle-sync.md`
|
||||
- 요약: Todo 이후 In Progress 실행, 하위 티켓, plan/code-review, 완료/폐기 흐름은 사용자가 명시적으로 해제할 때까지 구현 잠금 상태로 둔다.
|
||||
|
||||
- [스케치] MCP Control Surface Sketch
|
||||
- 경로: `agent-roadmap/phase/agent-ops-mcp-control-plane/milestones/mcp-control-surface-sketch.md`
|
||||
- 요약: 외부 agent 제어 표면을 MCP로 둘지 여부와 tool policy, 권한, side effect 경계를 현재 sync/action core 정리 이후 검토할 후보 항목으로 보류한다.
|
||||
|
||||
## Phase 경계
|
||||
|
||||
- 이 Phase는 현재 Workflow Core 구현의 선행 조건이 아니다.
|
||||
- Core task lifecycle, proto-socket client-core 통신, provider adapter 기본 통합, Flutter workbench UX가 먼저 닫힌 뒤 진행한다.
|
||||
- Plane/Jira work item과 agent-roadmap Milestone item의 양방향 동기화는 별도 Core sync domain이 소유하며, provider adapter 세부 구현과 분리한다.
|
||||
- Sync는 Plane/Jira provider project 단위 project sync 설정을 통해 provider project target, git remote, 실제 작업 workspace를 확정한 뒤 진행한다.
|
||||
- MCP 서버, 외부 agent tool policy, roadmap/action side effect 제어는 이 Phase에서 다룬다.
|
||||
- 외부 agent 제어 표면, tool policy, roadmap/action side effect 제어는 후속 스케치에서만 검토하며 현재 진행 마일스톤의 확정 범위가 아니다.
|
||||
- IOP 내부 모델 라우팅, RAG, context compression, output validation은 NomadCode 범위에서 제외한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
# Milestone: MCP Control Surface Sketch
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
외부 agent가 NomadCode Core를 제어하는 표면을 MCP로 둘지 여부를 나중에 검토할 스케치로 보류한다. 현재 확정된 구현 범위가 아니며, Roadmap Driven Agent-Ops Automation과 이후 lifecycle sync에서 Core action, sync domain, review gate 경계가 더 분명해진 뒤 채택 여부와 구현 단위를 판단한다.
|
||||
|
||||
## 상태
|
||||
|
||||
[스케치]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- [ ] 외부 agent 제어 표면이 실제 제품/운영 흐름에서 필요한 사용 사례를 정리한다.
|
||||
- [ ] MCP, HTTP, CLI/internal command 같은 후보 표면을 비교하고 채택/비채택 결정 기준을 남긴다.
|
||||
- [ ] tool 권한, side effect, dry-run, expected revision, idempotency, actor/audit 경계를 정의한다.
|
||||
- [ ] 구현이 필요하다고 판단될 경우 하나의 계획 Milestone으로 자를 수 있는 후속 slice를 정한다.
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- SDD: 확인 필요 - 외부 agent 제어 표면은 API/tool 계약, 권한, side effect, audit 경계를 바꿀 수 있으므로 계획 승격 시 SDD 필요 여부를 다시 판단한다.
|
||||
- 결정 필요:
|
||||
- [ ] 외부 agent 제어 표면을 MCP로 채택할지 여부
|
||||
- [ ] 외부 agent가 호출할 수 있는 Core action 범위
|
||||
- [ ] 인증, 권한, audit, side effect 승인 경계
|
||||
|
||||
## 범위
|
||||
|
||||
- 외부 agent 제어 표면 후보 비교
|
||||
- MCP 채택/비채택 기준 정리
|
||||
- tool policy, 권한, side effect, idempotency 질문 정리
|
||||
- Core action 모델이 안정된 뒤 구현 Milestone으로 승격할지 판단
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [surface-sketch] External agent control surface sketch
|
||||
|
||||
외부 agent 제어 표면을 확정하지 않고 후보와 승격 조건만 정리한다.
|
||||
|
||||
- [ ] [surface-use-cases] 외부 agent가 Core action을 직접 호출해야 하는 사용 사례와 호출하지 않아도 되는 사용 사례를 구분한다.
|
||||
- [ ] [surface-options] MCP, HTTP, CLI/internal command 후보를 같은 기준으로 비교한다.
|
||||
- [ ] [tool-policy] 권한, actor, audit, side effect 승인, idempotency, revision mismatch 처리 질문을 정리한다.
|
||||
- [ ] [promotion-slice] 채택 시 첫 구현 단위와 선행 조건을 계획 Milestone 후보로 나눈다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 없음
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- MCP server 구현
|
||||
- Core action 구현 또는 HTTP API 변경
|
||||
- provider sync 동작 변경
|
||||
- 현재 진행중 Milestone의 완료 조건 변경
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 이 항목은 현재 Roadmap Driven Agent-Ops Automation에서 제외한 MCP 관련 내용을 나중에 검토하기 위한 스케치다.
|
||||
- 선행 후보: Roadmap Driven Agent-Ops Automation, Milestone Execution Lifecycle Sync
|
||||
- 관련 경로 후보: `services/core/internal/http/`, `packages/contracts/`, `agent-roadmap/`
|
||||
- 확인 필요: 외부 agent 제어 표면이 실제로 필요한지, 필요하다면 MCP가 NomadCode의 기본 표면이어야 하는지
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
# Milestone: Plane-Origin Authoring Roundtrip Sync
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/agent-ops-mcp-control-plane/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
Plane `Backlog + AGENT assignee` 상위 티켓에서 시작한 Milestone authoring이 IOP Responses workspace agent 실행, `develop` push, Gito HTTP webhook delivery, Core develop scan, provider identity match, Plane `Todo` projection까지 한 사이클로 수렴하도록 최신 dev 환경 기준으로 다시 닫는다. 사용자 메시지는 단순 authoring 입력으로 유지하고, provider identity와 push/projection 책임은 Core/runtime의 결정적 처리로 옮긴다.
|
||||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- 없음
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- SDD: 필요
|
||||
- SDD 문서: `agent-roadmap/sdd/agent-ops-mcp-control-plane/plane-origin-authoring-roundtrip-sync/SDD.md`
|
||||
- SDD 사유: 외부 provider 쓰기, IOP 실행 호출, git push, Gito webhook, identity map, retry/timeout, live smoke가 모두 걸린 runtime sync 마일스톤이다.
|
||||
- 잠금 해제 조건:
|
||||
- [ ] SDD 잠금이 해제되어 있다.
|
||||
- [ ] SDD 사용자 리뷰가 없거나 승인/해결되었다.
|
||||
- [ ] Acceptance Scenario가 아래 기능 Task와 연결되어 있다.
|
||||
- [ ] Evidence Map이 plan의 `Spec Targets`와 완료 시 `Spec Completion`으로 검증 가능하게 연결되어 있다.
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- Plane-origin work item intake 이후 authoring 실행과 `develop` 반영 사이의 실제 runtime handoff
|
||||
- IOP OpenAI-compatible Responses `/v1/responses` 호출의 long-running authoring 처리, timeout, queue timeout, stale monitoring 기준
|
||||
- authoring 프롬프트를 사용자 요청 중심으로 얇게 유지하면서 provider identity를 Milestone 문서 또는 sync ledger에 결정적으로 연결하는 방식
|
||||
- authoring 후 별도 push 단계가 필요할 때 IOP workspace agent에 push-only 요청을 보내고, remote `develop` 반영을 Core가 재검증하는 흐름
|
||||
- Gito HTTP webhook delivery를 wakeup으로 받아 Core가 local develop checkout을 fetch/scan하고 creation sync job으로 넘기는 흐름
|
||||
- Plane 원문 댓글 보존, 본문/제목 갱신, `Todo` 이동, task/slot terminal state까지 이어지는 live-cycle smoke
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [authoring-runtime] IOP authoring runtime contract
|
||||
|
||||
Plane-origin authoring이 짧은 사용자 입력과 workspace metadata만으로 실행되되, 장시간 작업과 push 분리를 runtime이 감당하도록 정리한다.
|
||||
|
||||
- [ ] [thin-prompt] 기본 authoring instruction은 `다음 메시지를 바탕으로 마일스톤을 작성해.` 수준으로 유지하고, Plane/provider 계약 세부를 사용자 프롬프트에 섞지 않는다. 검증: `services/core/internal/authoring` 테스트에서 기본 instruction이 얇고 provider 계약 문구가 포함되지 않음을 확인한다.
|
||||
- [ ] [iop-long-run] IOP Responses 호출이 10분 이상 걸릴 수 있는 작업을 실패로 조기 확정하지 않도록 timeout, retry, queue timeout, stale 판단 기준을 재정의한다. 검증: `MODEL_TIMEOUT_SEC`/`WORKFLOW_TASK_TIMEOUT_SEC` 설정과 scheduler authoring state가 long-running 작업을 `failed`로 조기 전환하지 않고 관찰 가능한 상태를 남긴다.
|
||||
- [ ] [iop-progress] `/v1/responses` streaming 지원 여부를 dev IOP 계약으로 확인하고, stream 미지원 환경에서는 `authoring_run_updated_at`과 task metadata 기반 stale monitor로 대체한다. 검증: stream 미지원 응답이 silent failure가 아니라 명시 사유로 기록되고, stream 지원 시에는 진행 갱신 중단을 감지할 수 있다.
|
||||
- [ ] [push-command] authoring 결과 작성 이후 Core가 workspace agent에 `변경된 내용에 대해 develop 브런치에 푸시해` push-only 요청을 별도 단계로 보낼 수 있다. 검증: 첫 authoring 응답 성공만으로 task 완료를 단정하지 않고, push-only 단계와 remote `develop` 확인이 있어야 다음 단계로 넘어간다.
|
||||
|
||||
### Epic: [identity-sync] Provider identity and develop scan
|
||||
|
||||
사용자 프롬프트에 provider 계약을 넣지 않고도 pushed Milestone이 원래 Plane 티켓과 매칭되도록 identity 연결을 결정적으로 보장한다.
|
||||
|
||||
- [ ] [identity-source] Plane work item identity는 task external ref, payload `work_item`, checkout metadata, project sync 설정에서 가져오고 사용자 작성 문구에 의존하지 않는다. 검증: `BuildAuthoringGenerateInput`이 workspace metadata를 유지하고, identity source 누락 시 authoring 또는 projection이 명시 오류로 멈춘다.
|
||||
- [ ] [identity-write] authoring 산출물에 `## Provider identity` block이 없더라도 Core/runtime이 provider, tenant, project, work item id, milestone id를 결정적으로 주입하거나 동등한 sync ledger를 만들어 `gitosync` scan이 매칭할 수 있게 한다. 검증: provider identity 없는 generated Milestone이 Gito callback 이후 조용히 drop되지 않고, match 가능 상태 또는 명시 failure로 수렴한다.
|
||||
- [ ] [develop-match] pushed `develop` revision에서 실제 Milestone path 변경과 provider identity가 원래 Plane 티켓과 일치할 때만 Plane projection을 실행한다. 검증: slot-local dirty, push 실패, identity mismatch, milestone path 없음은 Plane `Todo` projection으로 넘어가지 않는다.
|
||||
- [ ] [idempotency-retry] 같은 Plane 티켓, 같은 Milestone path, 같은 Gito revision 재처리가 중복 Milestone/중복 Plane mutation 없이 남은 단계만 재시도한다. 검증: `roadmap_sync_identities`, `roadmap_sync_steps`, Gito revision dedup이 부분 실패 복구와 충돌 중단을 구분한다.
|
||||
|
||||
### Epic: [gito-plane] Gito callback and Plane projection
|
||||
|
||||
Gito HTTP webhook wakeup 이후 Core sync pipeline이 Plane Todo projection까지 닫히는지 실제 환경 기준으로 검증한다.
|
||||
|
||||
- [ ] [gito-delivery] Forgejo push -> Gito -> NomadCode Core signed HTTP webhook delivery가 remote `develop` push마다 들어온다. 검증: Gito subscription target, signed delivery 202, Core gito webhook log, duplicate delivery 처리 결과를 secret 없이 확인한다.
|
||||
- [ ] [scan-enqueue] Gito wakeup이 local develop checkout fetch/scan 이후 `roadmap_creation_sync` job enqueue로 이어진다. 검증: provider identity가 있는 Milestone 변경은 정확히 하나의 creation sync job으로 이어지고, identity 없는 변경은 명시 사유를 남긴다.
|
||||
- [ ] [plane-todo] creation sync job이 Plane 원문 댓글 보존, Milestone 본문 projection, `[milestone-id]` 제목 갱신, `Todo` 이동까지 완료한다. 검증: Plane UI/API와 `roadmap_sync_steps`에서 `develop_matched`, `original_comment_preserved`, `plane_body_updated`, `plane_todo_moved`를 확인한다.
|
||||
- [ ] [slot-finalize] projected, failed, timeout, queue timeout, push failed, identity missing 결과가 workspace slot 상태와 task metadata에 일관되게 반영된다. 검증: 성공은 slot `available`, 수동 복구가 필요한 git 상태는 `dirty`, bridge/provider 오류는 `error`로 수렴한다.
|
||||
|
||||
### Epic: [live-gate] End-to-end live smoke gate
|
||||
|
||||
이 마일스톤은 unit/fake seam만으로 닫지 않고 실제 dev 환경에서 한 사이클 증거를 남긴다.
|
||||
|
||||
- [ ] [remote-baseline] standard remote runner, Core compose, IOP workspace clone, Gito service, Forgejo webhook, project sync DB 설정을 현재 요청 기준으로 reset/clean/reclone 또는 rebuild해 맞춘다. 검증: dirty runner 상태를 차단 사유로 삼지 않고 최신 local 작업 범위와 같은 테스트 환경으로 만든다.
|
||||
- [ ] [full-cycle] 새 Plane Backlog 티켓을 AGENT에 assign한 뒤 Core task 생성, IOP authoring, push-only 단계, remote `develop` commit, Gito callback, roadmap sync identity/steps, Plane `Todo` projection까지 확인한다. 검증: `agent-test/live-cycle-smoke.md` 절차에 따라 secret redaction 상태의 live evidence가 남는다.
|
||||
- [ ] [regression-doc] 이번 회귀 원인과 방지 기준을 `agent-test/live-cycle-smoke.md` 또는 core README의 비밀 없는 운영 규칙에 반영한다. 검증: provider identity 누락, IOP queue timeout, push-only 단계 누락, Gito delivery-only 성공을 각각 완료 조건으로 오인하지 않는 기준이 문서화된다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 없음
|
||||
- 검토 항목:
|
||||
- [ ] SDD gate가 해제되었다.
|
||||
- [ ] 모든 기능 Task와 검증이 충족되었다.
|
||||
- [ ] 실제 dev Plane-origin full-cycle smoke에서 Plane `Todo` projection까지 확인했다.
|
||||
- [ ] 사용자 완료 확인과 archive 승인을 받았다.
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- Plane `Todo -> In Progress` 이후 실행 lifecycle
|
||||
- Milestone Task를 Plane 하위 티켓으로 생성하거나 plan/code-review 루프에 연결하는 작업
|
||||
- Agent-origin Milestone 생성 후 Plane 티켓을 만드는 역방향 생성 sync
|
||||
- IOP 내부 모델 라우팅, queue 구현, RAG, output validation 변경
|
||||
- 외부 agent 제어 표면 또는 MCP tool policy 확정
|
||||
- 사용자 승인 없는 archive 이동
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `services/core/internal/authoring/`, `services/core/internal/adapters/openai/`, `services/core/internal/scheduler/`, `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`, `services/core/internal/projectsync/`, `services/core/internal/workitempipeline/`, `services/core/internal/http/`, `services/core/internal/storage/`, `services/core/README.md`, `agent-test/live-cycle-smoke.md`, `packages/contracts/notes/flutter-core-api-candidates.md`
|
||||
- 표준선(선택): Plane 티켓 본문은 사용자 요청 원문이고, authoring 프롬프트도 사용자 요청 중심으로 둔다. provider identity, project sync 설정, push 검증, Gito delivery, Plane projection은 사용자 프롬프트가 아니라 Core/runtime의 결정적 책임이다.
|
||||
- 선행 작업: `Milestone Work Item Creation Sync`, `Plane Work Item Webhook Intake`, `Gito HTTP Webhook Consumer Readiness`
|
||||
- 후속 작업: `Milestone Execution Lifecycle Sync`
|
||||
- 현재 관찰:
|
||||
- 최신 dev smoke에서 Plane webhook과 Core task 생성, IOP Responses 호출, Gito delivery 자체는 관찰되었다.
|
||||
- IOP `/v1/responses`는 현재 non-streaming 경로이며, 장시간/queue timeout 상황을 authoring 실패로 조기 확정할 위험이 있다.
|
||||
- 얇은 authoring prompt로 생성된 Milestone에는 provider identity block이 빠질 수 있고, 현재 `gitosync` scanner는 identity 없는 Milestone을 projection 대상으로 삼지 않는다.
|
||||
- 첫 authoring 호출만으로는 `develop` push가 보장되지 않으며, push-only workspace agent 요청과 remote `develop` 재검증이 필요하다.
|
||||
- Gito webhook 202만으로는 Plane 동기화 완료가 아니며, `roadmap_sync_identities`, `roadmap_sync_steps`, Plane `Todo` 상태까지 확인해야 한다.
|
||||
- 핸즈오프(2026-06-20):
|
||||
- 현재 타겟은 이 마일스톤이다. 로컬 `agent-roadmap/current.md`의 활성 Milestone은 `Plane-Origin Authoring Roundtrip Sync` 하나로 정리되어 있다.
|
||||
- 이 세션에서는 마일스톤 문서 작성과 Phase/ROADMAP 연결만 수행했다. 구현 plan, SDD 본문, 코드 변경 착수는 하지 않았다.
|
||||
- 다음 세션은 AGENTS 규칙을 다시 읽은 뒤 이 마일스톤과 `agent-test/live-cycle-smoke.md`, `services/core/internal/authoring/`, `services/core/internal/adapters/openai/`, `services/core/internal/gitosync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`를 우선 확인한다.
|
||||
- 구현 착수 전 SDD를 먼저 작성/해제해야 한다. 이유는 IOP 호출, git push, Gito webhook, Plane provider write, identity map, retry/timeout, live smoke가 한 경계에 묶여 있기 때문이다.
|
||||
- 중요한 방향: Plane 티켓 본문과 authoring prompt는 사용자 요청 원문 중심으로 얇게 유지한다. provider identity, project sync 설정, push-only 단계, Gito callback, Plane Todo projection은 사용자 프롬프트가 아니라 Core/runtime의 결정적 책임으로 설계한다.
|
||||
- 현재 알려진 회귀: IOP authoring은 milestone 파일을 만들 수 있지만 remote `develop` push가 별도 조치 없이는 보장되지 않고, 생성된 Milestone에 `## Provider identity` block이 없으면 `gitosync` scanner가 projection 대상으로 삼지 않는다.
|
||||
- 검증 기준: Gito webhook 202나 remote `develop` commit만으로 완료로 보지 않는다. `roadmap_sync_identities`, `roadmap_sync_steps`의 단계 기록과 Plane `Todo` 상태까지 확인해야 full-cycle 완료다.
|
||||
- 원격 테스트 runner는 disposable mirror로 취급한다. dirty 상태를 이유로 멈추지 말고 reset/clean/reclone 또는 파일 동기화 후 현재 요청 기준 상태로 맞춰 테스트한다.
|
||||
- 이 workspace에는 이미 여러 변경이 섞여 있으므로 다음 세션은 `git status --short`와 대상 파일 diff를 먼저 보고, 관련 없는 사용자/이전 변경을 되돌리지 않는다.
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
## 목표
|
||||
|
||||
NomadCode가 로드맵을 중심으로 사용자 입력, 실행 상태, 출력 검토, 완료 승인까지 한 흐름에서 운영하도록 Roadmap Operations Control Plane의 방향을 정한다. Plane/Jira 같은 work item provider는 사용자가 작업 상태를 움직이는 primary control UI가 될 수 있으며, NomadCode Core는 provider command와 agent-roadmap 변경을 같은 sync domain에서 검증해 Milestone item과 work item이 동일한 구조로 수렴하도록 한다. agent-roadmap은 장기 원장으로 유지하고, 외부 에이전트는 MCP-first 제어 표면을 통해 Core를 호출하며, agent-ops 스킬은 문서 작성, 의미 해석, 변경 제안, MCP 호출 준비 계층으로 낮춘다.
|
||||
NomadCode가 로드맵을 중심으로 사용자 입력, 실행 상태, 출력 검토, 완료 승인까지 한 흐름에서 운영하도록 Roadmap Operations Control Plane의 방향을 정한다. Plane/Jira 같은 work item provider는 사용자가 작업 상태를 움직이는 primary control UI가 될 수 있으며, NomadCode Core는 provider command와 agent-roadmap 변경을 같은 sync domain에서 검증해 Milestone item과 work item이 동일한 구조로 수렴하도록 한다. agent-roadmap은 장기 원장으로 유지하고, agent-ops 스킬은 문서 작성, 의미 해석, 변경 제안 계층으로 낮춘다. 외부 agent 제어 표면은 이 마일스톤의 확정 범위가 아니며 후속 스케치에서 별도로 검토한다.
|
||||
|
||||
## 상태
|
||||
|
||||
|
|
@ -26,8 +26,7 @@ NomadCode가 로드맵을 중심으로 사용자 입력, 실행 상태, 출력
|
|||
## 범위
|
||||
|
||||
- NomadCode Core가 품을 roadmap/action core 책임 정의
|
||||
- MCP-first 외부 agent 제어 표면 방향 정의
|
||||
- HTTP API와 MCP의 역할 분리
|
||||
- HTTP API, provider webhook, internal integration의 역할 경계 정의
|
||||
- 사용자 입력, 출력 검토, 승인, 보완, archive 확인을 NomadCode 라인에서 처리하는 흐름 정의
|
||||
- Plane 상위 티켓 1개를 Milestone 1개로 보고, Plane 하위 티켓을 Milestone 기능 Task로 투영하는 사용자 플로우 계약 정의
|
||||
- Plane/Jira 같은 provider work item과 agent-roadmap Milestone item 사이의 양방향 동기화 도메인 책임, 변경 감지, 수렴 정책 정의
|
||||
|
|
@ -35,43 +34,41 @@ NomadCode가 로드맵을 중심으로 사용자 입력, 실행 상태, 출력
|
|||
- Plane-origin authoring에서 workspace agent를 IOP CLI 1차 통로로 실행하는 기준 정의
|
||||
- Plane 상태 `Backlog`, `Todo`, `In Progress`, `User Review`, `Done`, `Cancelled`를 roadmap/agent-task lifecycle command로 해석하는 기준 정의
|
||||
- `Backlog + AGENT assignee`에서 Milestone 초안을 작성하고 Plane 티켓을 `Todo` 검토 상태로 옮긴 뒤, 사용자가 `In Progress`로 옮길 때 실제 실행과 하위 티켓 생성을 시작하는 gate 정의
|
||||
- agent-ops roadmap skills를 작성/제안/MCP 호출 준비 계층으로 낮추는 방향 정의
|
||||
- agent-ops roadmap skills를 작성/제안 계층으로 낮추는 방향 정의
|
||||
- completion event, `USER_REVIEW.md`, 완료 리뷰, dependency lock을 Core action으로 다루는 기준 정의
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [control-plane] Roadmap operations control plane
|
||||
|
||||
NomadCode 내부 core logic이 roadmap 기반 작업 운영을 소유하고, 외부 agent는 MCP를 통해 제어하는 구조를 정리한다.
|
||||
NomadCode 내부 core logic이 roadmap 기반 작업 운영을 소유하고, provider/UI/agent 입력을 검증 가능한 Core action으로 수렴시키는 구조를 정리한다.
|
||||
|
||||
- [ ] [core-state] roadmap, Phase, Milestone, plan, code-review, completion event, approval 상태를 Core가 다루는 state model과 revision/idempotency 계약으로 정리한다.
|
||||
- [ ] [core-actions] validate, position, propose/apply change, transition, archive, dependency check, completion event ingest를 Core action 후보로 정의한다.
|
||||
- [ ] [mcp-tools] 외부 agent용 MCP tool 표면을 Core action wrapper로 정의하고 `dry_run`, `expected_revision`, `idempotency_key`, `actor`, `reason` 입력 원칙을 정한다.
|
||||
- [ ] [http-role] HTTP API는 Flutter UI, webhook, internal integration용 표면으로 유지하고, 외부 agent 제어는 MCP-first로 여는 기준을 정한다.
|
||||
- [ ] [http-role] HTTP API는 Flutter UI, webhook, internal integration용 표면으로 유지하고, 실행 side effect는 Core action의 revision/idempotency 계약을 거치도록 하는 기준을 정한다.
|
||||
- [ ] [review-gates] 사용자 입력, 출력 검토, `USER_REVIEW.md`, 완료 승인, archive 승인을 NomadCode workflow 안의 review gate로 처리하는 흐름을 정한다.
|
||||
- [ ] [plane-control-flow] Plane을 primary control UI로 쓰는 lifecycle을 정리한다. 검증: `Backlog + AGENT assignee -> Todo -> In Progress -> User Review -> Done/Cancelled` 상태가 Milestone 초안, 사용자 검토, 실행 시작, 사용자 검토, 완료/archive 또는 폐기로 어떻게 연결되는지 문서에서 일관되게 읽힌다.
|
||||
- [ ] [plane-identity] Plane 상위 티켓을 Milestone, 하위 티켓을 Milestone 기능 Task로 매핑하는 id 계약을 정한다. 검증: Milestone id는 파일명으로 유지하고, Plane work item id는 외부 provider id로 보존하는 기준이 명확하다.
|
||||
- [ ] [todo-draft-gate] `Backlog + AGENT assignee`를 Milestone 초안 생성/갱신 trigger로 정의하고, `develop` branch의 agent-roadmap에 반영된 뒤 Plane 티켓을 `Todo`로 이동하는 gate를 정한다. 검증: Todo 진입은 자동 실행이 아니라 `develop`에 존재하는 Milestone의 사용자 검토 단계임이 명확하다.
|
||||
- [x] [plane-control-flow] Plane을 primary control UI로 쓰는 lifecycle을 정리한다. 검증: `Backlog + AGENT assignee -> Todo -> In Progress -> User Review -> Done/Cancelled` 상태가 Milestone 초안, 사용자 검토, 실행 시작, 사용자 검토, 완료/archive 또는 폐기로 어떻게 연결되는지 문서에서 일관되게 읽힌다.
|
||||
- [x] [plane-identity] Plane 상위 티켓을 Milestone, 하위 티켓을 Milestone 기능 Task로 매핑하는 id 계약을 정한다. 검증: Milestone id는 파일명으로 유지하고, Plane work item id는 외부 provider id로 보존하는 기준이 명확하다.
|
||||
- [x] [todo-draft-gate] `Backlog + AGENT assignee`를 Milestone 초안 생성/갱신 trigger로 정의하고, `develop` branch의 agent-roadmap에 반영된 뒤 Plane 티켓을 `Todo`로 이동하는 gate를 정한다. 검증: Todo 진입은 자동 실행이 아니라 `develop`에 존재하는 Milestone의 사용자 검토 단계임이 명확하다.
|
||||
- [ ] [in-progress-exec-gate] 사용자가 Plane 상위 티켓을 `In Progress`로 옮길 때 실제 실행과 하위 티켓 전환을 시작하는 기준을 정한다. 검증: Milestone Task가 Plane 하위 티켓으로 생성되고 plan/code-review 루프로 들어가는 시점이 명확하다.
|
||||
- [ ] [cancelled-discard] 사용자가 Plane 상위 티켓을 `Cancelled`로 옮길 때 Milestone 초안 폐기 또는 보류를 처리하는 기준을 정한다. 검증: Plane 폐기 상태가 agent-roadmap의 `[폐기]` 또는 `[보류]` 중 어느 상태로 연결되는지와 사용자 승인 없는 archive 이동 금지가 설명된다.
|
||||
- [ ] [child-task-review-policy] Milestone에서 만들어진 하위 작업은 막혔을 때만 `User Review`로 보내고, 정상 PASS 시 기본적으로 `Done`으로 보내는 정책을 정한다. 검증: 하위 작업의 기본 완료 흐름과 blocker review 흐름이 구분된다.
|
||||
- [ ] [milestone-review-loop] 모든 하위 작업이 완료되면 상위 Milestone 티켓을 `User Review`로 보내고, 사용자가 `Done`으로 옮기면 완료/archive, `Todo`로 되돌리면 보완 루프로 처리하는 기준을 정한다.
|
||||
- [ ] [skill-adapter] agent-ops roadmap skills가 action owner가 아니라 semantic authoring/proposal layer와 MCP call preparation layer가 되도록 축소 기준을 정한다.
|
||||
- [ ] [first-slice] 첫 구현 단위를 `roadmap.validate`, `roadmap.get_position`, `roadmap.ingest_completion_event` 중 어떤 순서로 자를지 정한다.
|
||||
|
||||
### Epic: [sync-domain] Milestone/work item sync domain
|
||||
|
||||
Plane/Jira 같은 provider work item과 agent-roadmap Milestone item을 동일한 작업 구조로 유지하는 별도 Core sync domain을 정의한다.
|
||||
|
||||
- [ ] [sync-owner] sync domain의 소유 책임을 정의한다. 검증: provider adapter는 native API read/write만 담당하고, Milestone/work item identity mapping, revision 비교, 수렴 적용은 Core sync domain 책임으로 읽힌다.
|
||||
- [x] [sync-owner] sync domain의 소유 책임을 정의한다. 검증: provider adapter는 native API read/write만 담당하고, Milestone/work item identity mapping, revision 비교, 수렴 적용은 Core sync domain 책임으로 읽힌다.
|
||||
- [ ] [sync-identity] Milestone, Epic/Task item id, provider work item id, parent-child 관계, provider revision, roadmap revision을 묶는 identity 계약을 정한다. 검증: Plane/Jira 어느 쪽에서 시작해도 같은 Milestone item 구조로 매핑되는 기준이 명확하다.
|
||||
- [ ] [sync-project-config] provider project 단위 sync 설정 모델과 저장 경계를 정한다. 검증: provider project target, git remote URL, source-of-truth branch, workspace id/path가 Core DB에 저장되는 project sync 설정으로 정의되고, 설정 누락 또는 중복 시 생성 동기화를 진행하지 않는 기준이 있다.
|
||||
- [x] [sync-project-config] provider project 단위 sync 설정 모델과 저장 경계를 정한다. 검증: provider project target, git remote URL, source-of-truth branch, workspace id/path가 Core DB에 저장되는 project sync 설정으로 정의되고, 설정 누락 또는 중복 시 생성 동기화를 진행하지 않는 기준이 있다.
|
||||
- [ ] [sync-detection] provider webhook, provider polling, roadmap file/index scan, agent-task completion event를 같은 변경 감지 입력으로 정리한다. 검증: 양쪽 중 어느 쪽에서 변경이 발생해도 sync event로 정규화되는 흐름이 문서화되어 있다.
|
||||
- [ ] [sync-convergence] 한쪽 변경을 다른 쪽에 반영해 Milestone item과 work item이 동일 형태로 수렴하는 apply 정책을 정한다. 검증: 생성, 제목/설명, 상태, parent-child, 완료/검토 흐름이 양방향으로 어떻게 반영되는지 설명된다.
|
||||
- [ ] [sync-conflict] 동시 수정, revision mismatch, 삭제/archive, 사용자 승인 필요 변경의 conflict 처리 정책을 정한다. 검증: Core가 조용히 덮어쓰지 않고 review gate 또는 dry-run proposal로 멈추는 기준이 있다.
|
||||
- [ ] [sync-schedule] sync domain의 scheduler 책임을 정한다. 검증: webhook이 없는 provider나 누락 이벤트를 주기 polling으로 보정하고, 같은 idempotency/revision 계약을 쓰는 방향이 명확하다.
|
||||
- [ ] [sync-contract] `packages/contracts`에 sync event/action 후보를 남긴다. 검증: `roadmap_sync.inspect`, `roadmap_sync.apply`, `roadmap_sync.changed` 같은 후보 표면과 필수 입력 필드가 compatibility note에 정리되어 있다.
|
||||
- [ ] [plane-origin-flow] Plane-origin Milestone 생성 시나리오를 정한다. 검증: Backlog 티켓 본문을 입력으로 IOP CLI 1차 통로를 통해 실행된 workspace agent가 같은 Milestone 파일 작성/push 경로에서 roadmap skill로 Milestone 초안을 만들고, `develop` 반영 후 sync layer가 pushed Milestone의 provider/work item identity를 검증한 뒤 원본 본문은 `사용자 요청:` 댓글로 보존하며, Plane 본문은 `develop`의 Milestone 내용으로 치환하고, 제목은 `[milestone-id] 제목` 형식으로 바꾼 뒤 Todo로 이동하는 순서가 문서화되어 있다.
|
||||
- [x] [plane-origin-flow] Plane-origin Milestone 생성 시나리오를 정한다. 검증: Backlog 티켓 본문을 입력으로 IOP CLI 1차 통로를 통해 실행된 workspace agent가 같은 Milestone 파일 작성/push 경로에서 roadmap skill로 Milestone 초안을 만들고, `develop` 반영 후 sync layer가 pushed Milestone의 provider/work item identity를 검증한 뒤 원본 본문은 `사용자 요청:` 댓글로 보존하며, Plane 본문은 `develop`의 Milestone 내용으로 치환하고, 제목은 `[milestone-id] 제목` 형식으로 바꾼 뒤 Todo로 이동하는 순서가 문서화되어 있다.
|
||||
- [ ] [agent-origin-flow] Agent-origin Milestone 생성 시나리오를 정한다. 검증: 에이전트 대화로 생성된 Milestone이 `develop` branch에 반영되었을 때 Plane parent ticket을 Todo 상태로 생성하고 identity map을 연결하는 흐름이 문서화되어 있다.
|
||||
- [ ] [sync-idempotency] Plane-origin과 Agent-origin 생성의 중복 방지와 부분 실패 복구 정책을 정한다. 검증: 같은 Plane 티켓 또는 같은 Milestone path가 재처리되어도 중복 Milestone/티켓을 만들지 않고, 댓글 보존/본문 치환/제목 변경/상태 이동 중 실패한 단계를 재시도할 수 있다.
|
||||
|
||||
|
|
@ -87,7 +84,8 @@ Plane/Jira 같은 provider work item과 agent-roadmap Milestone item을 동일
|
|||
|
||||
## 범위 제외
|
||||
|
||||
- IOP 내부 모델 라우팅, MCP/tool policy, output validation, RAG, context compression 구현
|
||||
- IOP 내부 모델 라우팅, output validation, RAG, context compression 구현
|
||||
- 외부 agent 제어 표면과 tool policy 확정 또는 구현
|
||||
- 전체 Flutter UI 구현
|
||||
- Project sync 설정 관리 UI/UX 실제 구현
|
||||
- Plane/Jira/Mattermost provider projection 세부 구현, Plane webhook/하위 티켓 생성 adapter 실제 구현. 단, Milestone/work item sync domain의 provider-neutral 책임과 계약 후보 정의는 포함한다.
|
||||
|
|
@ -97,12 +95,16 @@ Plane/Jira 같은 provider work item과 agent-roadmap Milestone item을 동일
|
|||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `services/core/internal/workflow/`, `services/core/internal/scheduler/`, `services/core/internal/http/`, `services/core/internal/workitem/`, `services/core/internal/db/`, `services/core/migrations/`, `services/core/queries/`, `packages/contracts/`, `agent-roadmap/`, `agent-ops/skills/common/update-roadmap/SKILL.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- 표준선(선택): `develop` branch의 `agent-roadmap`을 Milestone sync의 source of truth로 둔다. Plane/Jira 같은 work item provider는 사용자의 primary control UI와 projection 표면이 될 수 있고, provider 상태 변화와 agent-roadmap 변경은 NomadCode Core sync domain이 검증해 실행하는 command로 본다. NomadCode Core는 roadmap/action side effect, Milestone/work item identity mapping, idempotency/revision 검증, conflict review gate를 소유하고, agent-ops 스킬은 사용자의 자연어와 문서 초안을 Core/MCP 호출 입력으로 정리한다.
|
||||
- 표준선(선택): `develop` branch의 `agent-roadmap`을 Milestone sync의 source of truth로 둔다. Plane/Jira 같은 work item provider는 사용자의 primary control UI와 projection 표면이 될 수 있고, provider 상태 변화와 agent-roadmap 변경은 NomadCode Core sync domain이 검증해 실행하는 command로 본다. NomadCode Core는 roadmap/action side effect, Milestone/work item identity mapping, idempotency/revision 검증, conflict review gate를 소유하고, agent-ops 스킬은 사용자의 자연어와 문서 초안을 Core action 제안 입력으로 정리한다.
|
||||
- 프로젝트 설정 기준: Sync는 Plane/Jira provider project 단위로 묶으며, 각 project sync 설정은 provider project target, git remote URL, source-of-truth branch, 실제 작업 workspace를 Core DB에 저장한다. 이 설정은 이후 Project settings UI/UX에서 확인/수정할 수 있어야 한다.
|
||||
- 로컬 제어 표면 후보: `http://127.0.0.1:8080/v1`
|
||||
- 선행 작업: Workflow Core, External Integration, Project Workspace Management UX, 로드맵 스킬 운영 복잡도 평가
|
||||
- 후속 작업: `Milestone Work Item Creation Sync`, MCP tool contract 작성
|
||||
- 현재 지점: Agent-Ops MCP Control Plane이 진행중 Phase가 되었고, 실제 구현은 `Milestone Work Item Creation Sync` slice부터 시작한다. 이 마일스톤은 roadmap/action core 책임, Milestone/work item sync domain, MCP-first 제어 표면을 구현 가능한 Core action 후보로 정리하는 상위 설계/계약 문서다.
|
||||
- 후속 작업: `Milestone Execution Lifecycle Sync`, 외부 agent 제어 표면 스케치
|
||||
- 현재 지점: 이 Phase의 `Milestone Work Item Creation Sync`, `Plane Work Item Webhook Intake`, Gito branch/webhook wakeup 연동은 완료되어 archive에 남아 있다. 이 마일스톤은 roadmap/action core 책임과 Milestone/work item sync domain을 구현 가능한 Core action 후보로 정리하는 상위 설계/계약 문서다.
|
||||
- 완료 체크 근거:
|
||||
- `[plane-control-flow]`, `[plane-identity]`, `[todo-draft-gate]`, `[plane-origin-flow]`: Phase 흐름에 완료로 남은 `Milestone Work Item Creation Sync`와 `Plane Work Item Webhook Intake`가 Plane-origin Milestone 작성, Todo projection, provider identity 보존, Backlog+AGENT webhook intake까지 닫은 근거다.
|
||||
- `[sync-owner]`: 현재 문서의 동기화 도메인 방향이 provider adapter와 Core sync domain 책임을 분리하고, 완료된 Plane/Gito sync slice가 이 경계를 따라 구현되어 있다.
|
||||
- `[sync-project-config]`: project sync 설정 기준이 현재 문서에 남아 있고, Core 내부 경로 `services/core/internal/projectsync/`, `services/core/internal/roadmapsync/`, `services/core/internal/roadmapsyncpipeline/`가 provider project target, repository, workspace 기반 동기화 경계를 구현한다.
|
||||
- 실행 경계: 이 마일스톤은 상위 방향과 계약 정리용이다. 실제 구현은 `Milestone Work Item Creation Sync`부터 진행하고, `Milestone Execution Lifecycle Sync`는 사용자가 해제할 때까지 잠근다.
|
||||
- 동기화 도메인 방향:
|
||||
- Sync domain은 provider adapter보다 위에 위치하며, provider native DTO를 직접 소유하지 않는다.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ test_env: local
|
|||
test_profile: live-cycle-smoke
|
||||
domain: core
|
||||
verification_type: smoke
|
||||
last_rule_updated_at: 2026-06-16
|
||||
last_rule_updated_at: 2026-06-20
|
||||
---
|
||||
|
||||
# live-cycle-smoke
|
||||
|
|
@ -22,10 +22,11 @@ last_rule_updated_at: 2026-06-16
|
|||
- Core server: Docker Compose (`services/core/docker-compose.yml`), host port `18010->8080`.
|
||||
- Plane dev: `https://plane.toki-labs.com`, workspace `general`, project `NOMAD`.
|
||||
- Secrets: `.env.plane.local` (ignored), SOPS `secrets/nomadcode.dev.sops.yaml` (webhook secret).
|
||||
- runner checkout: 검증용 disposable mirror다. dirty worktree, uncommitted 변경, 임시 파일을 이유로 중단하지 않고 현재 대화 workspace의 요청 범위 상태와 같게 reset/clean/reclone 또는 파일 동기화한 뒤 테스트한다.
|
||||
|
||||
## 전제 조건
|
||||
|
||||
- Core server가 리뷰된 커밋으로 빌드·배포되어 있어야 한다.
|
||||
- Core server는 현재 검증 대상 상태로 빌드·배포되어 있어야 한다. 원격 checkout이나 실행 컨테이너가 낡았으면 테스트 수행자가 reset/clean/reclone, 파일 동기화, compose rebuild/recreate로 최신화한다.
|
||||
- `PLANE_WEBHOOK_SECRET`, `PLANE_TOKEN` 등 secret은 `.env.plane.local` 또는 SOPS에서 로드한다.
|
||||
- Plane dev 서버가 `https://plane.toki-labs.com`에서 정상 동작 중이어야 한다.
|
||||
- workspace slot 행이 DB에 `available` 상태로 최소 1개 이상 있어야 한다.
|
||||
|
|
@ -129,8 +130,8 @@ curl -s http://127.0.0.1:18010/api/tasks/e3c92411-4056-4b76-a879-66f7904125d0
|
|||
|
||||
## 차단 기준
|
||||
|
||||
- Core server가 리뷰된 커밋으로 실행 중이지 않으면 `live-creation-projection`과 slot 회수 증거를 수집할 수 없다.
|
||||
- Docker image 재빌드·재배포는 user-owned 인프라 액션이다 (push → remote pull → build → restart).
|
||||
- Core server를 현재 검증 대상 상태로 재배포할 수 없으면 `live-creation-projection`과 slot 회수 증거를 수집할 수 없다.
|
||||
- 원격 runner checkout의 dirty 상태는 차단 기준이 아니다. reset/clean/reclone, 파일 동기화, Docker image 재빌드·재배포를 먼저 수행하고, 권한·secret·외부 서비스 장애처럼 에이전트가 해소할 수 없는 사유만 차단으로 보고한다.
|
||||
|
||||
## 보고 항목
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ COPY . .
|
|||
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/nomadcode-core ./cmd/server
|
||||
|
||||
FROM alpine:3.21
|
||||
RUN apk add --no-cache ca-certificates git
|
||||
RUN adduser -D -H appuser
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ NomadCode Core는 사용자 요청을 작업 단위로 받고, 작업 상태를
|
|||
|
||||
로컬 실행은 현재 개발 호스트의 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`를 제공해야 합니다. 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` 입니다.
|
||||
모델 호출의 기본 방향은 IOP Edge의 OpenAI-compatible Responses input surface입니다. 기본 dev endpoint는 `MODEL_BASE_URL=http://toki-labs.com:18083/v1`, 기본 model route는 `MODEL_NAME=codex`입니다. NomadCode는 OpenAI-compatible request/response shape를 기본 계약으로 유지하고, task/workspace/session/approval/artifact/notification 같은 IOP/NomadCode 전용 실행 문맥은 별도 `iop` wrapper field가 아니라 요청 `metadata` 확장으로 전달하는 방향을 기준으로 합니다. `MODEL_API_KEY`가 비어 있으면 Authorization header를 보내지 않습니다. 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
|
||||
|
||||
|
|
@ -82,15 +82,15 @@ DATABASE_URL="postgres://user:password@localhost:5432/dbname?sslmode=disable" ./
|
|||
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_BASE_URL="http://toki-labs.com:18083/v1" \
|
||||
MODEL_API_KEY="" \
|
||||
MODEL_NAME="codex" \
|
||||
MODEL_CONTEXT_SIZE="0" \
|
||||
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로만 취급합니다.
|
||||
모델 호출은 OpenAI-compatible Responses API의 non-streaming `POST /v1/responses` 형식을 사용합니다. `MODEL_BASE_URL`이 `/v1`까지만 가리키면 Core가 `/responses`를 붙여 호출합니다. NomadCode의 task/workspace/session 문맥은 OpenAI-compatible 표면을 깨는 별도 top-level wrapper가 아니라 `metadata` 확장으로 전달합니다. workspace-bound authoring에서는 slot checkout path가 `metadata.workspace` flat string으로 전달됩니다. direct Ollama 호환 경로에서는 `MODEL_CONTEXT_SIZE`를 Ollama 전용 option인 `options.num_ctx`로 전달할 수 있지만, 기본 dev IOP Edge 호출에서는 `0`으로 둡니다.
|
||||
|
||||
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 작업에서 기본화 여부를 다시 결정합니다.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ services:
|
|||
REDIS_KEY_PREFIX: ${DEV_REDIS_KEY_PREFIX:-nomadcode-core:dev}
|
||||
AUTH_USERNAME: ${AUTH_USERNAME:-nomadcode}
|
||||
AUTH_PASSWORD: ${AUTH_PASSWORD:-}
|
||||
MODEL_BASE_URL: ${MODEL_BASE_URL:-http://192.168.0.91:11434}
|
||||
MODEL_API_KEY: ${MODEL_API_KEY:-ollama}
|
||||
MODEL_NAME: ${MODEL_NAME:-qwen3.6:35b-a3b-bf16}
|
||||
MODEL_CONTEXT_SIZE: ${MODEL_CONTEXT_SIZE:-262144}
|
||||
MODEL_BASE_URL: ${MODEL_BASE_URL:-http://toki-labs.com:18083/v1}
|
||||
MODEL_API_KEY: ${MODEL_API_KEY:-}
|
||||
MODEL_NAME: ${MODEL_NAME:-codex}
|
||||
MODEL_CONTEXT_SIZE: ${MODEL_CONTEXT_SIZE:-0}
|
||||
MODEL_TIMEOUT_SEC: ${MODEL_TIMEOUT_SEC:-300}
|
||||
A2A_EDGE_URL: ${A2A_EDGE_URL:-}
|
||||
A2A_AGENT_URL: ${A2A_AGENT_URL:-}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultAPIKey = "ollama"
|
||||
defaultTimeoutSec = 300
|
||||
responsesPath = "/v1/responses"
|
||||
)
|
||||
|
|
@ -37,9 +36,6 @@ type Client struct {
|
|||
}
|
||||
|
||||
func NewClient(cfg Config, logger *slog.Logger) *Client {
|
||||
if cfg.APIKey == "" {
|
||||
cfg.APIKey = defaultAPIKey
|
||||
}
|
||||
if cfg.TimeoutSec <= 0 {
|
||||
cfg.TimeoutSec = defaultTimeoutSec
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,25 @@ func TestGenerateWorkspaceMetadataUsesFlatWorkspacePath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateOmitsAuthorizationWhenAPIKeyEmpty(t *testing.T) {
|
||||
var gotAuth string
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotAuth = r.Header.Get("Authorization")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"id":"r1","model":"codex","output_text":"ok","usage":{"total_tokens":1}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(Config{BaseURL: server.URL, Model: "codex"}, nil)
|
||||
if _, err := client.Generate(context.Background(), model.GenerateInput{Input: "say hello"}); err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
if gotAuth != "" {
|
||||
t.Fatalf("Authorization: got %q, want empty", gotAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateExtractsOutputContent(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ type pipelinePayload struct {
|
|||
Instructions string `json:"instructions"`
|
||||
}
|
||||
|
||||
const defaultAuthoringInstructions = "다음 메시지를 바탕으로 마일스톤을 작성해."
|
||||
|
||||
// isAuthoringTask returns true when the task is a Plane-origin authoring task
|
||||
// that should be routed through the IOP workspace authoring bridge. The criteria
|
||||
// are: source=="plane" and task metadata contains a "checkout" key with a
|
||||
|
|
@ -147,7 +149,7 @@ func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool)
|
|||
ident := getWorkItemIdentity(task, p)
|
||||
|
||||
if instructions == "" {
|
||||
instructions = buildAuthoringInstructions(task, checkout, ident)
|
||||
instructions = defaultAuthoringInstructions
|
||||
}
|
||||
|
||||
return model.GenerateInput{
|
||||
|
|
@ -169,38 +171,3 @@ func BuildAuthoringGenerateInput(task storage.Task) (model.GenerateInput, bool)
|
|||
},
|
||||
}, true
|
||||
}
|
||||
|
||||
func buildAuthoringInstructions(task storage.Task, checkout workitempipeline.CheckoutMetadata, ident workItemIdentity) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("You are a workspace agent running inside a NomadCode slot checkout.\n")
|
||||
b.WriteString("Your task is to author or update the agent-roadmap Milestone file for the following Plane work item.\n\n")
|
||||
|
||||
b.WriteString("Please strictly follow these rules:\n")
|
||||
b.WriteString("- One Plane top-level work item maps to exactly one agent-roadmap Milestone.\n")
|
||||
b.WriteString("- First search active Milestones for a Provider identity block matching provider/tenant/project/work item id; update that file if found.\n")
|
||||
b.WriteString("- If none exists, create one Milestone under the active Phase and include a `## Provider identity` block.\n")
|
||||
b.WriteString("- The block must include provider, tenant, project, work item id, and milestone id/roadmap item id when known.\n")
|
||||
b.WriteString("- Commit and push the Milestone file change to develop from the slot checkout.\n")
|
||||
b.WriteString("- Do not create duplicate Milestones or Plane child work items in this slice.\n\n")
|
||||
|
||||
b.WriteString("Work item details:\n")
|
||||
b.WriteString("Title: " + task.Title + "\n")
|
||||
b.WriteString("Provider: " + ident.Provider + "\n")
|
||||
if ident.Tenant != "" {
|
||||
b.WriteString("Tenant: " + ident.Tenant + "\n")
|
||||
}
|
||||
if ident.Project != "" {
|
||||
b.WriteString("Project: " + ident.Project + "\n")
|
||||
}
|
||||
b.WriteString("Work Item ID: " + ident.WorkItemID + "\n")
|
||||
if ident.ExternalURL != "" {
|
||||
b.WriteString("External URL: " + ident.ExternalURL + "\n")
|
||||
}
|
||||
if ident.StateID != "" {
|
||||
b.WriteString("State ID: " + ident.StateID + "\n")
|
||||
}
|
||||
b.WriteString("\nCheckout details:\n")
|
||||
b.WriteString("Workspace slot path: " + checkout.SlotPath + "\n")
|
||||
b.WriteString("Source branch: " + checkout.SourceBranch + "\n")
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,8 +128,8 @@ func TestBuildAuthoringGenerateInputFallbackInstructions(t *testing.T) {
|
|||
if input.Input != "My work item title" {
|
||||
t.Errorf("unexpected input fallback: %q", input.Input)
|
||||
}
|
||||
if input.Instructions == "" {
|
||||
t.Error("expected fallback instructions to be set")
|
||||
if input.Instructions != defaultAuthoringInstructions {
|
||||
t.Errorf("unexpected fallback instructions: %q", input.Instructions)
|
||||
}
|
||||
if input.WorkspaceMetadata.Path != "/home/user/workspace/nomadcode/slots/001" {
|
||||
t.Errorf("unexpected workspace path: %q", input.WorkspaceMetadata.Path)
|
||||
|
|
@ -153,9 +153,9 @@ func TestBuildAuthoringGenerateInputNonPlaneSource(t *testing.T) {
|
|||
|
||||
func TestBuildAuthoringGenerateInputMissingCheckout(t *testing.T) {
|
||||
task := storage.Task{
|
||||
ID: "task-4",
|
||||
Title: "Plane task without checkout",
|
||||
Source: "plane",
|
||||
ID: "task-4",
|
||||
Title: "Plane task without checkout",
|
||||
Source: "plane",
|
||||
Metadata: json.RawMessage(`{"agent_run_state":"running"}`),
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ func TestBuildAuthoringGenerateInputCarriesProviderIdentityContext(t *testing.T)
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthoringGenerateInputFallbackInstructionsRequireIdentityBlockAndNoDuplicate(t *testing.T) {
|
||||
func TestBuildAuthoringGenerateInputDefaultInstructionsStayThin(t *testing.T) {
|
||||
task := storage.Task{
|
||||
ID: "task-2",
|
||||
Title: "My work item title",
|
||||
|
|
@ -248,20 +248,22 @@ func TestBuildAuthoringGenerateInputFallbackInstructionsRequireIdentityBlockAndN
|
|||
t.Fatal("expected authoring task to be detected")
|
||||
}
|
||||
|
||||
assertions := []string{
|
||||
"Provider identity block",
|
||||
"duplicate",
|
||||
"active Milestone",
|
||||
"develop",
|
||||
if input.Instructions != defaultAuthoringInstructions {
|
||||
t.Fatalf("unexpected default instructions: %q", input.Instructions)
|
||||
}
|
||||
|
||||
for _, ast := range assertions {
|
||||
if !containsIgnoreCase(input.Instructions, ast) {
|
||||
t.Errorf("expected instructions to contain %q, but got:\n%s", ast, input.Instructions)
|
||||
forbidden := []string{
|
||||
"Plane",
|
||||
"Provider identity",
|
||||
"duplicate",
|
||||
"develop",
|
||||
"Work item details",
|
||||
"Checkout details",
|
||||
}
|
||||
|
||||
for _, text := range forbidden {
|
||||
if strings.Contains(input.Instructions, text) {
|
||||
t.Errorf("default instructions should not contain %q: %q", text, input.Instructions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,10 +79,10 @@ func Load() Config {
|
|||
RedisKeyPrefix: os.Getenv("REDIS_KEY_PREFIX"),
|
||||
AuthUsername: getEnv("AUTH_USERNAME", "nomadcode"),
|
||||
AuthPassword: os.Getenv("AUTH_PASSWORD"),
|
||||
ModelBaseURL: getEnv("MODEL_BASE_URL", "http://192.168.0.91:11434"),
|
||||
ModelAPIKey: getEnv("MODEL_API_KEY", "ollama"),
|
||||
ModelName: getEnv("MODEL_NAME", "qwen3.6:35b-a3b-bf16"),
|
||||
ModelContextSize: getEnvInt("MODEL_CONTEXT_SIZE", 262144),
|
||||
ModelBaseURL: getEnv("MODEL_BASE_URL", "http://toki-labs.com:18083/v1"),
|
||||
ModelAPIKey: os.Getenv("MODEL_API_KEY"),
|
||||
ModelName: getEnv("MODEL_NAME", "codex"),
|
||||
ModelContextSize: getEnvInt("MODEL_CONTEXT_SIZE", 0),
|
||||
ModelTimeoutSec: getEnvInt("MODEL_TIMEOUT_SEC", 300),
|
||||
A2AEdgeURL: firstEnv("A2A_EDGE_URL", "A2A_AGENT_URL"),
|
||||
A2AAgentURL: firstEnv("A2A_AGENT_URL", "A2A_EDGE_URL"),
|
||||
|
|
|
|||
|
|
@ -32,16 +32,16 @@ func TestLoadA2AEdgeURLFallsBackToLegacyAgentURL(t *testing.T) {
|
|||
func TestLoadModelAndA2ADefaults(t *testing.T) {
|
||||
cfg := Load()
|
||||
|
||||
if cfg.ModelBaseURL != "http://192.168.0.91:11434" {
|
||||
if cfg.ModelBaseURL != "http://toki-labs.com:18083/v1" {
|
||||
t.Fatalf("ModelBaseURL: got %q", cfg.ModelBaseURL)
|
||||
}
|
||||
if cfg.ModelAPIKey != "ollama" {
|
||||
t.Fatalf("ModelAPIKey: got %q", cfg.ModelAPIKey)
|
||||
if cfg.ModelAPIKey != "" {
|
||||
t.Fatalf("ModelAPIKey: got %q, want empty", cfg.ModelAPIKey)
|
||||
}
|
||||
if cfg.ModelName != "qwen3.6:35b-a3b-bf16" {
|
||||
if cfg.ModelName != "codex" {
|
||||
t.Fatalf("ModelName: got %q", cfg.ModelName)
|
||||
}
|
||||
if cfg.ModelContextSize != 262144 {
|
||||
if cfg.ModelContextSize != 0 {
|
||||
t.Fatalf("ModelContextSize: got %d", cfg.ModelContextSize)
|
||||
}
|
||||
if cfg.ModelTimeoutSec != 300 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue