docs(edge): quickstart OpenAI 포트 일관성을 18081로 보정하고 review follow-up 아카이브
docs/edge-local-dev-guide.md의 18083 포트 예시를 18081로 수정하여 openai.listen과 일관성을 맞춘다. 또한 관련 review follow-up 작업을 archive로 정리하고, openai_text_tool_call_boundary 새 plan/review를 추가한다.
This commit is contained in:
parent
d8fa80e7bc
commit
ad77aa3afb
9 changed files with 1095 additions and 297 deletions
|
|
@ -0,0 +1,166 @@
|
|||
<!-- task=review_followup_alignment/02_docs_runtime_context_split plan=1 tag=REVIEW_REVIEW_DOCS -->
|
||||
|
||||
# Code Review Reference - REVIEW_REVIEW_DOCS
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-06
|
||||
task=review_followup_alignment/02_docs_runtime_context_split, plan=1, tag=REVIEW_REVIEW_DOCS
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior active task: `agent-task/review_followup_alignment/02_docs_runtime_context_split`
|
||||
- Archived plan: `agent-task/review_followup_alignment/02_docs_runtime_context_split/plan_local_G02_0.log`
|
||||
- Archived review: `agent-task/review_followup_alignment/02_docs_runtime_context_split/code_review_local_G02_0.log`
|
||||
- Verdict: FAIL
|
||||
- Required summary: `docs/edge-local-dev-guide.md:146`와 `docs/edge-local-dev-guide.md:155`의 `/v1/chat/completions` 예시가 `http://<edge-host>:18083`을 사용한다. 같은 공개 quickstart의 `openai.listen`과 기본 smoke/base URL은 `18081`이므로 두 예시를 `http://<edge-host>:18081/v1/chat/completions`로 맞추고 `18083` 잔존 검증을 추가해야 한다.
|
||||
- Suggested summary: 없음
|
||||
- Nit summary: 없음
|
||||
- Affected files: `docs/edge-local-dev-guide.md`
|
||||
- Verification evidence:
|
||||
- `rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md`: reviewer rerun, no output.
|
||||
- `rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md`: reviewer rerun, no output.
|
||||
- `rg --sort path -n '18083' docs/edge-local-dev-guide.md`: reviewer rerun, found lines 146 and 155.
|
||||
- Roadmap carryover: 없음
|
||||
- Narrow archive reread allowed if exact prior context is needed: `agent-task/review_followup_alignment/02_docs_runtime_context_split/plan_local_G02_0.log`, `agent-task/review_followup_alignment/02_docs_runtime_context_split/code_review_local_G02_0.log`
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G01.md` → `code_review_local_G01_N.log`, `PLAN-local-G01.md` → `plan_local_G01_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/review_followup_alignment/02_docs_runtime_context_split/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_REVIEW_DOCS-1] 공개 quickstart OpenAI 포트 일관성 보정 | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] REVIEW_REVIEW_DOCS-1의 공개 quickstart chat examples 포트를 `18081`로 맞춘다.
|
||||
- [x] 중간/최종 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G01.md`에 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리 `agent-task/review_followup_alignment/02_docs_runtime_context_split/`를 `agent-task/archive/2026/07/review_followup_alignment/02_docs_runtime_context_split/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/review_followup_alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. 남은 sibling active task가 있어 parent를 유지했다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
|
||||
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획에 제시된 대로 `docs/edge-local-dev-guide.md`의 `18083` 포트를 `sed -i`로 `18081`로 전역 교체했다. 추가 파일은 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
단일 Markdown 문서 내 포트 리터럴 값만 변경하는 단순 교체로, 별도 설계 결정은 없음.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 연결 대상: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 공개 quickstart의 모든 OpenAI-compatible 예시가 `openai.listen: 18081` 기준으로 일관되는가.
|
||||
- 공개 `docs/edge-local-dev-guide.md`에 dev-runtime 포트 `18083`이 남지 않는가.
|
||||
- 기존 private/local marker와 secret 원문 제거 검증도 계속 통과하는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### REVIEW_REVIEW_DOCS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n '18083' docs/edge-local-dev-guide.md
|
||||
(output) - no output, no matches
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com|18083' docs/edge-local-dev-guide.md
|
||||
(output) - no output, no matches
|
||||
|
||||
$ rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
(output) - no output, no matches
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Sections and their ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` to `[x]` only |
|
||||
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` to `[x]` only; final checkbox is mandatory before saving |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone `구현 잠금 > 결정 필요` item blocks implementation |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
|
||||
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included in stub |
|
||||
|
||||
## 코드리뷰 결과 - 2026-07-06
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- implementation deviation: Pass
|
||||
- verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계: PASS 종결. active plan/review를 로그로 아카이브하고 `complete.log` 작성 후 task 디렉터리를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<!-- task=review_followup_alignment/02_docs_runtime_context_split plan=0 tag=REVIEW_DOCS -->
|
||||
# CODE_REVIEW-local-G02: 공개 문서와 로컬 검증 문맥 분리
|
||||
date=2026-07-06
|
||||
|
||||
## 상태
|
||||
구현 완료
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
구현 후 이 파일의 구현 에이전트 소유 섹션을 실제 변경 내용, 계획 대비 변경, 검증 출력으로 채운다. active `PLAN-*.md`와 `CODE_REVIEW-*.md`는 남겨두고 review 준비 상태를 보고한다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`를 만들거나 archive/complete 처리를 하지 않는다.
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] REVIEW_DOCS-1의 공개 문서 축소와 local test 문맥 이동을 수행한다.
|
||||
- [x] 중간 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G02.md`에 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 archive 로그로 이동한다.
|
||||
- [x] active `PLAN-*-G??.md`를 archive 로그로 이동한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent 정리 여부를 확인한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- `docs/edge-local-dev-guide.md`: 공개 가이드로 완전히 재작성. `toki-labs.com`, `192.168.0.x` 대역, dev-runtime 경로, SSH host, bearer token 원문, machine-specific config 등을 제거하고 `<control-plane-host>`, `<edge-host>`, `<token>`, `<model-alias>`, `<platform>`, `<edge-id>` placeholder로 치환. `dev-runtime` 키워드 자체를 공개 docs에서 완전 제거. provider pool, thinking smoke, tool-call boundary smoke의 공개 개요만 남김.
|
||||
- `agent-test/local/edge-local-dev-guide.md` 신규 생성: `docs/`에서 이동한 private/local 검증 런버 세부사항을 새 local test 문서로 작성. 특정 host(`toki-labs.com`, `192.168.0.91`, `192.168.0.59`), dev-runtime 경로(`build/dev-runtime/edge.yaml`, `EDGE_BIN`), provider pool candidate, SSH 연결, credential 원문 예시(`IOP_MLX_API_KEY="<edge.yaml local bearer token>"`) 포함.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- 공개 docs/edge-local-dev-guide.md를 "Edge를 로컬에서 빌드하고 실행해 Node를 연결하고 OpenAI-compatible smoke까지 확인하는 최소 절차"로 정의. person-facing quickstart에 private host와 검증 evidence를 섞지 않음.
|
||||
- `dev-runtime` 키워드 자체를 tracked docs에서 완전히 제거. 이 키워드는 local test 문맥에서만 사용됨.
|
||||
- 이동된 local 문서는 secret 원문을 포함하지 않고 `<edge.yaml local bearer token>` 등 placeholder와 환경변수 이름만 남김.
|
||||
- 이미 `agent-test/local/edge-smoke.md`가 있으므로 중복 방지: 기존 edge-smoke.md에 없었던 Windows SSH, 내부 IP 검증, token 처리, raw tool-call curl 예시 등만 새로운 local guide로 이동.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 연결 대상: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
- tracked `docs/`가 공개 가이드 성격으로 정리되었는가.
|
||||
- private/local-only 검증 문맥이 local test 문서로 이동했는가.
|
||||
- 공개 문서에 secret 원문, private IP, machine-specific host가 남지 않는가.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### REVIEW_DOCS-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md
|
||||
(no output)
|
||||
|
||||
$ rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
(no output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md
|
||||
(no output)
|
||||
|
||||
$ rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
(no output)
|
||||
```
|
||||
|
||||
두 명령 모두 출력이 없어 예상과 일치한다. 공개 `docs/edge-local-dev-guide.md`에 `192.168.x.x`, `dev-runtime`, `edge-toki-labs`, `toki-labs.com`, `Bearer <token>`, `mattermost_credentials`가 모두 제거됨.
|
||||
|
||||
## 코드리뷰 결과
|
||||
_리뷰 에이전트가 append한다._
|
||||
|
||||
## 코드리뷰 결과 - 2026-07-06
|
||||
|
||||
- 종합 판정: FAIL
|
||||
- 차원별 평가:
|
||||
- correctness: Fail
|
||||
- completeness: Fail
|
||||
- test coverage: Pass
|
||||
- API contract: Fail
|
||||
- code quality: Pass
|
||||
- implementation deviation: Fail
|
||||
- verification trust: Fail
|
||||
- 발견된 문제:
|
||||
- Required: `docs/edge-local-dev-guide.md:146`와 `docs/edge-local-dev-guide.md:155`의 `/v1/chat/completions` 예시가 `http://<edge-host>:18083`을 사용한다. 같은 공개 quickstart의 config는 `openai.listen: "0.0.0.0:18081"`이고 기본 smoke/base URL도 `18081`을 가리키므로, 사용자가 문서를 그대로 따르면 thinking smoke만 다른 dev-runtime 포트를 호출한다. 공개 guide에서는 두 예시를 `http://<edge-host>:18081/v1/chat/completions`로 맞추고, `18083`이 공개 docs에 남지 않는 검증을 추가해야 한다.
|
||||
- 다음 단계: WARN/FAIL 후속 plan/review를 작성해 위 Required를 좁게 수정한다.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Complete - review_followup_alignment/02_docs_runtime_context_split
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-07-06
|
||||
|
||||
## 요약
|
||||
|
||||
공개 Edge local quickstart에서 private/local runtime 문맥을 분리하고, 후속 루프에서 OpenAI-compatible 예시 포트를 `18081`로 일관화했다. 총 2회 루프, 최종 판정 PASS.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G02_0.log` | `code_review_local_G02_0.log` | FAIL | 공개 docs 정리 후 `/v1/chat/completions` 예시 2곳에 dev-runtime 포트 `18083`이 남아 후속 수정 필요. |
|
||||
| `plan_local_G01_1.log` | `code_review_local_G01_1.log` | PASS | 두 chat examples를 `http://<edge-host>:18081/v1/chat/completions`로 맞추고 `18083` 잔존 검증을 통과. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `docs/edge-local-dev-guide.md`를 공개 quickstart 성격으로 정리하고 private host, dev-runtime, machine-specific runtime evidence를 분리했다.
|
||||
- local 검증 런북 문맥은 `agent-test/local/edge-local-dev-guide.md`로 이동했다.
|
||||
- 공개 quickstart의 OpenAI-compatible base URL, models smoke, chat completions 예시가 모두 `18081` 기준으로 일관되도록 보정했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `rg --sort path -n '18083' docs/edge-local-dev-guide.md` - PASS; no output.
|
||||
- `rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com|18083' docs/edge-local-dev-guide.md` - PASS; no output.
|
||||
- `rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md` - PASS; no output.
|
||||
- Go/runtime/full-cycle 검증 - 생략; 변경 범위가 Markdown 공개 문서 리터럴과 local test 문맥 분리라 deterministic `rg` 검증으로 닫음.
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<!-- task=review_followup_alignment/02_docs_runtime_context_split plan=1 tag=REVIEW_REVIEW_DOCS -->
|
||||
# PLAN-local-G01: 공개 quickstart OpenAI 포트 일관성 보정
|
||||
date=2026-07-06
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
구현 후 `CODE_REVIEW-local-G01.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채운다. active `PLAN-*.md`와 `CODE_REVIEW-*.md`는 남겨두고 review 준비 상태를 보고한다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실제 구현을 차단할 때만 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈춘다. 환경/secret/service blocker, 일반 범위 조정, 후속 에이전트가 닫을 수 있는 evidence gap은 사용자 리뷰 요청이 아니다.
|
||||
|
||||
## 배경
|
||||
이전 구현은 private/local marker 제거 검증은 통과했지만 공개 quickstart 안에 dev-runtime OpenAI 포트 `18083`이 두 곳 남았다. 같은 문서의 예시 config와 기본 smoke는 `openai.listen: 18081`을 기준으로 하므로, 공개 문서 자체의 실행 흐름이 불일치한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
구현 중 사용자 결정이 필요해 보여도 채팅으로 직접 묻지 않는다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 연결 대상, 차단 근거, 실행한 명령, 자동 후속 불가 이유, 재개 조건을 기록하고 멈춘다. 이 follow-up은 Milestone-linked 작업이 아니므로 정상 경로에서는 사용자 리뷰 요청이 없어야 한다.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior active task: `agent-task/review_followup_alignment/02_docs_runtime_context_split`
|
||||
- Archived plan: `agent-task/review_followup_alignment/02_docs_runtime_context_split/plan_local_G02_0.log`
|
||||
- Archived review: `agent-task/review_followup_alignment/02_docs_runtime_context_split/code_review_local_G02_0.log`
|
||||
- Verdict: FAIL
|
||||
- Required summary: `docs/edge-local-dev-guide.md:146`와 `docs/edge-local-dev-guide.md:155`의 `/v1/chat/completions` 예시가 `http://<edge-host>:18083`을 사용한다. 같은 공개 quickstart의 `openai.listen`과 기본 smoke/base URL은 `18081`이므로 두 예시를 `http://<edge-host>:18081/v1/chat/completions`로 맞추고 `18083` 잔존 검증을 추가해야 한다.
|
||||
- Suggested summary: 없음
|
||||
- Nit summary: 없음
|
||||
- Affected files: `docs/edge-local-dev-guide.md`
|
||||
- Verification evidence:
|
||||
- `rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md`: reviewer rerun, no output.
|
||||
- `rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md`: reviewer rerun, no output.
|
||||
- `rg --sort path -n '18083' docs/edge-local-dev-guide.md`: reviewer rerun, found lines 146 and 155.
|
||||
- Roadmap carryover: 없음
|
||||
- Narrow archive reread allowed if exact prior context is needed: `agent-task/review_followup_alignment/02_docs_runtime_context_split/plan_local_G02_0.log`, `agent-task/review_followup_alignment/02_docs_runtime_context_split/code_review_local_G02_0.log`
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/common/rules-roadmap.md`
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-ops/skills/common/router.md`
|
||||
- `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-ops/rules/project/domain/testing/rules.md`
|
||||
- `agent-contract/index.md`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `.gitignore`
|
||||
- `docs/edge-local-dev-guide.md`
|
||||
- `agent-test/local/edge-local-dev-guide.md`
|
||||
- `agent-task/review_followup_alignment/02_docs_runtime_context_split/plan_local_G02_0.log`
|
||||
- `agent-task/review_followup_alignment/02_docs_runtime_context_split/code_review_local_G02_0.log`
|
||||
|
||||
### SDD 기준
|
||||
not applicable. 이 작업은 비-roadmap 문서 follow-up이며 Roadmap Targets를 선언하지 않는다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
- test_env: local
|
||||
- `agent-test/local/rules.md` present/read.
|
||||
- `agent-ops/rules/project/domain/testing/rules.md` read because 검증 문맥과 `agent-test/local/**` 문서 경계가 관련된다.
|
||||
- 외부 runner, Docker, field/bootstrap 검증은 필요 없다.
|
||||
- 적용 검증은 현재 checkout에서 실행 가능한 deterministic `rg --sort path` 검색이다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
- 공개 docs에 dev-runtime 포트 `18083`이 남는지 막는 자동 테스트는 없다.
|
||||
- 이 follow-up의 최종 검증에 `18083` 잔존 검색을 포함한다.
|
||||
|
||||
### 심볼 참조
|
||||
none. Markdown 문서의 포트 예시만 수정한다.
|
||||
|
||||
### 분할 판단
|
||||
single plan. 수정 범위가 `docs/edge-local-dev-guide.md`의 두 URL과 검증 명령 보강으로 한정되어 dependency boundary, ownership boundary, API-vs-call-site split, 별도 risk profile이 없다.
|
||||
|
||||
### 범위 결정 근거
|
||||
`agent-test/local/edge-local-dev-guide.md`의 `18083`은 local/dev-runtime provider pool 문맥이므로 이번 follow-up 범위가 아니다. 계약 문서와 runtime code도 수정하지 않는다.
|
||||
|
||||
### 빌드 등급
|
||||
local-G01. 단일 Markdown 문서의 포트 일관성 보정이며 deterministic 검색으로 검증 가능하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
- [ ] REVIEW_REVIEW_DOCS-1의 공개 quickstart chat examples 포트를 `18081`로 맞춘다.
|
||||
- [ ] 중간/최종 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G01.md`에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### REVIEW_REVIEW_DOCS-1. 공개 quickstart OpenAI 포트 일관성 보정
|
||||
- 문제:
|
||||
- `docs/edge-local-dev-guide.md:56`은 OpenAI-compatible listen port를 `18081`로 제시한다.
|
||||
- `docs/edge-local-dev-guide.md:120`, `docs/edge-local-dev-guide.md:124`, `docs/edge-local-dev-guide.md:131`도 base URL을 `18081`로 제시한다.
|
||||
- `docs/edge-local-dev-guide.md:146`와 `docs/edge-local-dev-guide.md:155`만 `18083`을 사용해 공개 quickstart 실행 경로가 불일치한다.
|
||||
- 해결 방법:
|
||||
- 두 `/v1/chat/completions` 예시 URL을 `18081`로 바꾼다.
|
||||
- 변경 전:
|
||||
```bash
|
||||
curl -fsS http://<edge-host>:18083/v1/chat/completions \
|
||||
```
|
||||
- 변경 후:
|
||||
```bash
|
||||
curl -fsS http://<edge-host>:18081/v1/chat/completions \
|
||||
```
|
||||
- 수정 파일 및 체크리스트:
|
||||
- [ ] `docs/edge-local-dev-guide.md`: 두 chat examples의 URL port를 `18081`로 수정.
|
||||
- 테스트 작성:
|
||||
- [ ] 별도 테스트 파일은 만들지 않는다. Markdown 예시 정합성 수정이며 deterministic `rg` 검증으로 닫는다.
|
||||
- 중간 검증:
|
||||
- [ ] `rg --sort path -n '18083' docs/edge-local-dev-guide.md` 결과가 비어 있는지 확인한다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `docs/edge-local-dev-guide.md` | REVIEW_REVIEW_DOCS-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com|18083' docs/edge-local-dev-guide.md
|
||||
rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
```
|
||||
|
||||
예상 결과: 두 명령 모두 출력이 없다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -40,19 +40,19 @@ tracked `docs/`에는 사람용 최신 공개 가이드만 남기고, 개인/로
|
|||
# agent-test/local: 로컬 환경별 검증 런북
|
||||
```
|
||||
- 수정 파일 및 체크리스트:
|
||||
- [ ] `docs/edge-local-dev-guide.md`: 공개 가이드로 축소하고 private/local runtime 세부사항 제거.
|
||||
- [ ] `agent-test/local/edge-local-dev-guide.md` 또는 기존 local test profile: 필요한 로컬 검증 런북을 이동.
|
||||
- [x] `docs/edge-local-dev-guide.md`: 공개 가이드로 축소하고 private/local runtime 세부사항 제거.
|
||||
- [x] `agent-test/local/edge-local-dev-guide.md` 신규 생성: 필요한 로컬 검증 런북을 이동.
|
||||
- 테스트 작성:
|
||||
- [ ] 별도 테스트 파일은 만들지 않는다.
|
||||
- [ ] `rg` 검증으로 공개 docs에 private/local-only marker가 남지 않음을 확인한다.
|
||||
- [x] 별도 테스트 파일은 만들지 않는다.
|
||||
- [x] `rg` 검증으로 공개 docs에 private/local-only marker가 남지 않음을 확인한다.
|
||||
- 중간 검증:
|
||||
- [ ] `rg --sort path -n '192\\.168\\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\\.com' docs/edge-local-dev-guide.md` 결과가 비어 있는지 확인한다.
|
||||
- [ ] `rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md` 결과가 비어 있는지 확인한다.
|
||||
- [x] `rg --sort path -n '192\\.168\\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\\.com' docs/edge-local-dev-guide.md` 결과가 비어 있는지 확인한다.
|
||||
- [x] `rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md` 결과가 비어 있는지 확인한다.
|
||||
|
||||
## 구현 체크리스트
|
||||
- [ ] REVIEW_DOCS-1의 공개 문서 축소와 local test 문맥 이동을 수행한다.
|
||||
- [ ] 중간 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G02.md`에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
- [x] REVIEW_DOCS-1의 공개 문서 축소와 local test 문맥 이동을 수행한다.
|
||||
- [x] 중간 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G02.md`에 기록한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 최종 검증
|
||||
```bash
|
||||
|
|
@ -61,4 +61,8 @@ rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-loca
|
|||
```
|
||||
예상 결과: 두 명령 모두 출력이 없다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
## 구현 완료
|
||||
- `docs/edge-local-dev-guide.md` 공개 가이드로 재작성 완료 (334줄 → 173줄).
|
||||
- `agent-test/local/edge-local-dev-guide.md` 신규 생성 완료 (287줄, 15KB).
|
||||
- `CODE_REVIEW-local-G02.md` 구현 에이전트 소유 섹션 완료.
|
||||
- PLAN 구현 체크리스트, CODE_REVIEW 구현 체크리스트 모두 완료 체크.
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
<!-- task=openai_text_tool_call_boundary plan=0 tag=BUGFIX_TOOLCALL -->
|
||||
|
||||
# Code Review Reference - BUGFIX_TOOLCALL
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-07-06
|
||||
task=openai_text_tool_call_boundary, plan=0, tag=BUGFIX_TOOLCALL
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_N.log`, `PLAN-local-G06.md` → `plan_local_G06_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/openai_text_tool_call_boundary/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [BUGFIX_TOOLCALL-1] 실제 dev 실패 형태의 `edit` raw XML tool-call 회귀 테스트 | [ ] |
|
||||
| [BUGFIX_TOOLCALL-2] XML raw text tool-call parser scanner 강화 | [ ] |
|
||||
| [BUGFIX_TOOLCALL-3] tools streaming buffered validation 완화 | [ ] |
|
||||
| [BUGFIX_TOOLCALL-4] 검증과 dev smoke 계약 기록 | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [BUGFIX_TOOLCALL-1] 실제 dev 실패 형태의 `edit` raw XML tool-call 회귀 테스트를 추가한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-2] XML raw text tool-call parser를 regex-only에서 parameter body를 opaque text로 다루는 scanner로 강화하고 기존 합성 경로를 모두 같은 parser로 연결한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-3] `tools[]`가 있는 streaming chat completions를 retry 가능한 buffered validation 경로로 보내는 완화책을 추가한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-4] 로컬 회귀 테스트와 dev-runtime smoke 계약을 실행하고 결과를 review stub에 남긴다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다.
|
||||
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/openai_text_tool_call_boundary/`를 `agent-task/archive/YYYY/MM/openai_text_tool_call_boundary/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
|
||||
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 연결 대상: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 실제 dev 실패 fixture가 `edit` tool, `path`, `edits[]`, Markdown fence, literal `<edge-host>`를 포함하는지 확인한다.
|
||||
- parser가 parameter body를 opaque text로 다루면서 기존 malformed/unknown tool error semantics를 유지하는지 확인한다.
|
||||
- stream+tools 요청이 strict flag 없이도 retry 가능한 buffered validation 경로를 타는지 확인한다.
|
||||
- 성공 응답/SSE에 raw `<tool_call>`, escaped `\u003ctool_call`, sentinel이 새지 않는지 확인한다.
|
||||
- token/secret 원문이 plan, review, logs, test output에 남지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
|
||||
|
||||
### BUGFIX_TOOLCALL-1 중간 검증
|
||||
```bash
|
||||
$ go test ./apps/edge/internal/openai -run 'TestChatCompletions(SynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder|StreamSynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder)$'
|
||||
(output)
|
||||
```
|
||||
|
||||
### BUGFIX_TOOLCALL-2 중간 검증
|
||||
```bash
|
||||
$ go test ./apps/edge/internal/openai -run 'TestChatCompletions(SynthesizesToolCallsFromClineTextBlock|SynthesizesTextToolCallsForProviderRoute|SynthesizesProviderTextToolCallsWhenFallbackMarked|FailsMalformedTextToolCallAfterRetryLimit|SynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder)$'
|
||||
(output)
|
||||
```
|
||||
|
||||
### BUGFIX_TOOLCALL-3 중간 검증
|
||||
```bash
|
||||
$ go test ./apps/edge/internal/openai -run 'TestChatCompletions(ToolsStreamRetriesMalformedToolCallBeforeChunk|StrictBufferedStreamRetriesMalformedToolCallBeforeChunk|ProviderStreamBlocksMalformedTextToolCall)$'
|
||||
(output)
|
||||
```
|
||||
|
||||
### BUGFIX_TOOLCALL-4 중간 검증
|
||||
```bash
|
||||
$ go test ./apps/edge/internal/openai
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ go test ./apps/edge/internal/openai
|
||||
(output)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --branch --short && git log --oneline -1 && test -x build/dev-runtime/bin/edge && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --help >/tmp/iop-tool-boundary-refresh-help.txt && curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status >/tmp/iop-tool-boundary-status.json'
|
||||
(output)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_OPENAI_TOKEN:-}" && cat >/tmp/iop-tool-boundary-nonstream.json <<'"'"'JSON'"'"'
|
||||
{"model":"qwen3.6:35b","stream":false,"messages":[{"role":"user","content":"Reply with exactly one edit tool call for /tmp/example.md that writes a curl example using http://<edge-host>:18081/v1/chat/completions inside a markdown bash fence."}],"tools":[{"type":"function","function":{"name":"edit","parameters":{"type":"object","properties":{"path":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"newText":{"type":"string"}},"required":["newText"]}}},"required":["path","edits"],"additionalProperties":false}}}],"tool_choice":"auto"}
|
||||
JSON
|
||||
curl -sS http://127.0.0.1:18083/v1/chat/completions -H "Authorization: Bearer ${IOP_OPENAI_TOKEN}" -H "Content-Type: application/json" --data-binary @/tmp/iop-tool-boundary-nonstream.json >/tmp/iop-tool-boundary-nonstream.out
|
||||
python3 - <<'"'"'PY'"'"'
|
||||
from pathlib import Path
|
||||
s=Path("/tmp/iop-tool-boundary-nonstream.out").read_text()
|
||||
assert "<tool_call" not in s and "\\u003ctool_call" not in s, s
|
||||
assert "tool_calls" in s or "tool_validation_error" in s, s
|
||||
PY'
|
||||
(output)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_OPENAI_TOKEN:-}" && python3 - <<'"'"'PY'"'"' >/tmp/iop-tool-boundary-stream.json
|
||||
import json
|
||||
print(json.dumps({"model":"qwen3.6:35b","stream":True,"messages":[{"role":"user","content":"Reply with exactly one edit tool call for /tmp/example.md that writes a curl example using http://<edge-host>:18081/v1/chat/completions inside a markdown bash fence."}],"tools":[{"type":"function","function":{"name":"edit","parameters":{"type":"object","properties":{"path":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"newText":{"type":"string"}},"required":["newText"]}}},"required":["path","edits"],"additionalProperties":False}}}],"tool_choice":"auto"}))
|
||||
PY
|
||||
curl -sS http://127.0.0.1:18083/v1/chat/completions -H "Authorization: Bearer ${IOP_OPENAI_TOKEN}" -H "Content-Type: application/json" --data-binary @/tmp/iop-tool-boundary-stream.json >/tmp/iop-tool-boundary-stream.sse
|
||||
python3 - <<'"'"'PY'"'"'
|
||||
from pathlib import Path
|
||||
s=Path("/tmp/iop-tool-boundary-stream.sse").read_text()
|
||||
assert "<tool_call" not in s and "\\u003ctool_call" not in s, s
|
||||
assert "data: [DONE]" in s, s
|
||||
assert "tool_calls" in s or "tool_validation_error" in s, s
|
||||
PY'
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute archive/complete/archive move steps |
|
||||
| 구현 항목별 완료 여부 | Implementing agent checks only | Item text/order fixed |
|
||||
| 구현 체크리스트 | Implementing agent checks only | Final checkbox is mandatory |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
|
||||
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless selected Milestone lock decision blocks implementation |
|
||||
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus list |
|
||||
| 검증 결과 | Implementing agent | Paste actual stdout/stderr |
|
||||
| 코드리뷰 결과 | Review agent appends | Not included until review |
|
||||
416
agent-task/openai_text_tool_call_boundary/PLAN-local-G06.md
Normal file
416
agent-task/openai_text_tool_call_boundary/PLAN-local-G06.md
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
<!-- task=openai_text_tool_call_boundary plan=0 tag=BUGFIX_TOOLCALL -->
|
||||
|
||||
# Plan - BUGFIX_TOOLCALL
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 계획은 구현 루프의 입력이다. 구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active 파일을 그대로 둔 뒤 review ready를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.
|
||||
|
||||
선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막는 경우에만 review stub의 `사용자 리뷰 요청` 섹션을 정확한 근거로 채우고 멈춘다. 구현 중 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/service 준비, 일반 범위 조정, 반복 실패, 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과 또는 후속 plan으로 남긴다.
|
||||
|
||||
## 배경
|
||||
|
||||
2026-07-06 dev-runtime의 Pi 호출에서 `qwen3.6:35b` 응답이 raw XML text tool-call로 끝났고, IOP Edge가 이를 OpenAI-compatible `tool_calls`로 합성하는 중 `malformed tool call: missing function block or definition inside tool_call`를 냈다. dev Edge/Node/provider pool은 살아 있었고, client는 SSE error를 받은 쪽이다. 이번 작업은 같은 형태의 `edit` payload를 회귀 fixture로 고정하고, Edge의 raw text tool-call normalization이 복잡한 인자 본문과 streaming tool-use turn을 안전하게 처리하도록 한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
사용자 리뷰 요청은 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 직접 사용자 prompt를 만들지 않고, code-review가 정당성 검증과 `USER_REVIEW.md` 작성을 소유한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/common/rules-roadmap.md`
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/ROADMAP.md`
|
||||
- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
|
||||
- `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md`
|
||||
- `agent-ops/skills/common/router.md`
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||
- `agent-ops/rules/project/domain/edge/rules.md`
|
||||
- `agent-ops/rules/project/domain/testing/rules.md`
|
||||
- `agent-contract/index.md`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/dev/rules.md`
|
||||
- `agent-test/dev/edge-smoke.md`
|
||||
- `agent-test/dev/node-smoke.md`
|
||||
- `agent-test/dev/inventory.yaml`
|
||||
- `Makefile`
|
||||
- `apps/edge/internal/openai/types.go`
|
||||
- `apps/edge/internal/openai/stream.go`
|
||||
- `apps/edge/internal/openai/chat_handler.go`
|
||||
- `apps/edge/internal/openai/tool_validation.go`
|
||||
- `apps/edge/internal/openai/server_test.go`
|
||||
- `apps/edge/internal/openai/log_safety_test.go`
|
||||
|
||||
### SDD 기준
|
||||
|
||||
not applicable. 이 작업은 활성 `[스케치]` Milestone의 기능 Task 완료를 주장하지 않는 non-roadmap dev 회귀 bugfix다. 활성 Milestone `usage-token-log-ops-mvp`는 `구현 잠금: 잠금`이고 사용량/로그 운영 범위 스케치라, 이번 OpenAI-compatible raw text tool-call parser 회귀 수정과 직접 연결하지 않는다.
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- 선택 test_env: `dev`. 버그가 dev-runtime OpenAI-compatible `18083`에서 발생했고 `agent-test/dev/edge-smoke.md`가 `apps/edge/internal/openai/**` text tool-call 합성/validation 변경 시 non-stream/stream raw boundary smoke를 요구한다.
|
||||
- 적용 rule/profile: `agent-test/dev/rules.md`, `agent-test/dev/edge-smoke.md`, `agent-test/dev/node-smoke.md`, `agent-test/dev/inventory.yaml`.
|
||||
- local quick check 기준도 확인했다: `agent-test/local/rules.md`.
|
||||
- concrete local command: `go test ./apps/edge/internal/openai`는 현재 checkout에서 실행 가능하며 계획 작성 중 PASS 확인됨.
|
||||
- dev smoke source: `agent-test/dev/edge-smoke.md`의 raw text tool-call boundary 기준. token/secret 원문은 tracked artifact에 남기지 않는다.
|
||||
|
||||
테스트 환경 프리플라이트:
|
||||
|
||||
- runner: `ssh toki@toki-labs.com`
|
||||
- repo root: `/Users/toki/agent-work/iop-dev`
|
||||
- dev runtime identity: Edge id `edge-toki-labs-dev`, OpenAI base URL `http://toki-labs.com:18083/v1`, runner-local base URL `http://127.0.0.1:18083/v1`, Edge-Node TCP `toki-labs.com:18084`, admin `127.0.0.1:19093`
|
||||
- binary/config paths: `build/dev-runtime/bin/edge`, `build/dev-runtime/edge.yaml`
|
||||
- 2026-07-06 조사 증거: `/v1/models`는 auth 401로 port alive, Control Plane status는 3개 node healthy/idle, `18083`/`18084` TCP reachable.
|
||||
- 구현 후 dev smoke는 원격 checkout과 dev-runtime binary가 구현 commit을 반영한 뒤에만 현재 변경 검증으로 인정한다. 원격 HEAD가 다르거나 binary가 stale이면 dev-runtime clean sync/rebuild 또는 별도 배포 작업이 먼저 필요하다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 `server_test.go`는 단순 XML raw text tool-call, mustache tool-call, unknown/malformed 차단, non-stream retry, strict buffered stream retry를 다룬다.
|
||||
- 공백 1: 이번 실제 실패처럼 `edit` tool의 `edits` JSON 배열 안에 Markdown code fence, escaped JSON request body, `http://<edge-host>:18081/...` angle-bracket placeholder가 들어간 XML parameter fixture가 없다.
|
||||
- 공백 2: `collectXMLCandidateMatches`와 `collectXMLTextToolCallMatches`가 같은 scanner를 공유해야 하는 회귀가 없다.
|
||||
- 공백 3: `tools[]`가 있는 streaming 요청을 live SSE로 둬서 retry가 불가능한 경로가 계속 선택되는지 막는 정책 테스트가 없다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- parser 교체 시 함께 확인할 참조: `textToolCallBlockRE`, `textToolFunctionRE`, `textToolParameterRE`, `collectXMLCandidateMatches`, `collectXMLTextToolCallMatches`, `parseTextToolCallBlock`.
|
||||
- `rg --sort path -n "synthesizeToolCallsFromTextResult|collectXMLCandidateMatches|textTool|tool_validation_error|strict_stream_buffer|malformed tool call" apps/edge/internal/openai`로 call site와 테스트 위치를 확인했다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
split decision policy를 적용했다. 단일 plan으로 둔다.
|
||||
|
||||
- ownership boundary: `apps/edge/internal/openai` 한 패키지 안의 OpenAI-compatible raw text tool-call boundary다.
|
||||
- API-vs-call-site boundary: parser helper와 stream routing call site가 같은 public behavior를 형성하며, parser fixture 없이는 stream 완화만 따로 완료하기 어렵다.
|
||||
- test strategy: 모든 회귀가 `server_test.go`의 fake run service와 `go test ./apps/edge/internal/openai`로 검증된다.
|
||||
- risk profile: 성공 content에 raw `<tool_call>`이 새면 안 된다는 동일 계약 리스크다.
|
||||
- split하면 parser와 stream policy가 서로의 테스트 fixture를 공유해야 해서 review coordination 비용이 더 크다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 포함: `apps/edge/internal/openai/types.go`, `stream.go`, 필요 시 `tool_validation.go`, `server_test.go`.
|
||||
- 제외: node provider adapter, provider pool routing, token usage/log ledger, docs 포트 수정, Pi client 설정. 이번 증상은 client가 받은 Edge `tool_validation_error`이며, provider/Node outage 또는 client-side parse failure가 아니다.
|
||||
- 제외: secret/API key logging. 기존 `log_safety_test.go`의 preview field 금지를 유지한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`local-G06`. 변경은 한 Go 패키지에 갇히고 deterministic unit/regression test로 검증 가능하지만, parser state machine과 streaming retry 경로가 OpenAI-compatible 외부 계약에 닿아 중간 수준 리스크가 있다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [BUGFIX_TOOLCALL-1] 실제 dev 실패 형태의 `edit` raw XML tool-call 회귀 테스트를 추가한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-2] XML raw text tool-call parser를 regex-only에서 parameter body를 opaque text로 다루는 scanner로 강화하고 기존 합성 경로를 모두 같은 parser로 연결한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-3] `tools[]`가 있는 streaming chat completions를 retry 가능한 buffered validation 경로로 보내는 완화책을 추가한다.
|
||||
- [ ] [BUGFIX_TOOLCALL-4] 로컬 회귀 테스트와 dev-runtime smoke 계약을 실행하고 결과를 review stub에 남긴다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## BUGFIX_TOOLCALL-1 실제 실패 fixture 추가
|
||||
|
||||
### 문제
|
||||
|
||||
실제 dev failure는 `apps/edge/internal/openai/types.go:256`의 XML candidate parser가 모델의 raw `<tool_call>` 응답을 읽는 과정에서 발생했다. 현재 테스트는 단순 `<parameter=commands>` 중심이라, `edit` tool payload의 큰 JSON/Markdown/angle-bracket 본문이 같은 error를 재현하는지 고정하지 못한다.
|
||||
|
||||
실제 에러 케이스 예시:
|
||||
|
||||
````text
|
||||
Pi session: 2026-07-06T09:13:01.814Z
|
||||
provider/model: iop / qwen3.6:35b
|
||||
response_id: chatcmpl-manual-1783329323005263000
|
||||
stopReason: error
|
||||
errorMessage: malformed tool call: missing function block or definition inside tool_call
|
||||
|
||||
assistant content preview:
|
||||
두 줄을 `18083` -> `18081`로 수정하고 검증을 진행한다.
|
||||
|
||||
<tool_call>
|
||||
<function=edit>
|
||||
<parameter=path>
|
||||
/config/workspace/iop/docs/edge-local-dev-guide.md
|
||||
</parameter>
|
||||
<parameter=edits>
|
||||
[{"newText": "```bash\ncurl -fsS http://<edge-host>:18081/v1/chat/completions ..."}]
|
||||
</parameter>
|
||||
</function>
|
||||
</tool_call>
|
||||
````
|
||||
|
||||
핵심은 `<parameter=edits>` 본문이 JSON string array이고 그 안에 Markdown code fence, escaped JSON request body, literal `<edge-host>` placeholder가 들어간다는 점이다. 이 literal angle-bracket text는 tool-call XML tag로 해석하면 안 된다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`apps/edge/internal/openai/server_test.go`에 다음 성격의 테스트를 추가한다.
|
||||
|
||||
Before:
|
||||
|
||||
```go
|
||||
// apps/edge/internal/openai/server_test.go:514
|
||||
func TestChatCompletionsSynthesizesTextToolCallsForProviderRoute(t *testing.T) {
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "...\n<parameter=commands>\n[\"git status\"]\n</parameter>..."}
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
func TestChatCompletionsSynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder(t *testing.T) {
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "두 줄을 `18083` -> `18081`로 수정하고 검증을 진행한다.\n\n" + editToolCallFixture}
|
||||
// expect finish_reason tool_calls, function.name edit, path preserved,
|
||||
// edits decoded as JSON array, and no raw <tool_call> leak.
|
||||
}
|
||||
```
|
||||
|
||||
fixture 요구:
|
||||
|
||||
- tool schema: `edit` with required `path` string and `edits` array.
|
||||
- `edits[0].newText` contains `curl -fsS http://<edge-host>:18081/v1/chat/completions`.
|
||||
- response body must not contain raw `<tool_call>` or escaped `\u003ctool_call`.
|
||||
- `function.arguments` JSON must decode and preserve the literal `<edge-host>` inside `newText`.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: actual failure fixture helper/test 추가.
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: stream variant도 같은 fixture로 추가하거나 BUGFIX_TOOLCALL-3 테스트에서 재사용.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
작성한다. Test names:
|
||||
|
||||
- `TestChatCompletionsSynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder`
|
||||
- `TestChatCompletionsStreamSynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder`
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/edge/internal/openai -run 'TestChatCompletions(SynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder|StreamSynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder)$'
|
||||
```
|
||||
|
||||
Expected: PASS, no raw `<tool_call>` leak, decoded `edits[0].newText` contains literal `<edge-host>`.
|
||||
|
||||
## BUGFIX_TOOLCALL-2 XML parser scanner 강화
|
||||
|
||||
### 문제
|
||||
|
||||
`apps/edge/internal/openai/types.go:214`-`217`는 XML-ish tool-call parsing을 regex로 수행한다. `collectXMLCandidateMatches`는 `textToolCallBlockRE`로 block을 자르고 `textToolFunctionRE`와 `textToolParameterRE`를 별도로 적용한다. `collectXMLTextToolCallMatches`와 `parseTextToolCallBlock`도 같은 regex 계열을 다시 사용한다.
|
||||
|
||||
```go
|
||||
// apps/edge/internal/openai/types.go:214
|
||||
textToolCallBlockRE = regexp.MustCompile(`(?is)<\s*tool_call\s*>(.*?)<\s*/\s*tool_call\s*>`)
|
||||
// apps/edge/internal/openai/types.go:216
|
||||
textToolFunctionRE = regexp.MustCompile(`(?is)<\s*function\s*=\s*...`)
|
||||
// apps/edge/internal/openai/types.go:217
|
||||
textToolParameterRE = regexp.MustCompile(`(?is)<\s*parameter\s*=\s*...`)
|
||||
```
|
||||
|
||||
이 방식은 parameter 본문에 code fence, JSON string, angle-bracket placeholder가 섞인 실제 agent payload를 opaque body로 다루지 못하는 취약점이 있다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
XML 형식만 작은 scanner로 교체한다. mustache scanner는 이미 `findMustacheToolCallEnd`에서 quote/depth를 고려하므로 범위를 넓히지 않는다.
|
||||
|
||||
After approach:
|
||||
|
||||
```go
|
||||
type xmlTextToolCallBlock struct {
|
||||
start int
|
||||
end int
|
||||
name string
|
||||
params map[string]string
|
||||
err error
|
||||
}
|
||||
|
||||
func scanXMLTextToolCallBlocks(content string) ([]xmlTextToolCallBlock, bool) {
|
||||
// 1. top-level <tool_call> open을 찾는다.
|
||||
// 2. matching </tool_call>까지 block을 잡는다.
|
||||
// 3. block 안에서 top-level <function=name>...</function>을 찾는다.
|
||||
// 4. function body에서 <parameter=key>...</parameter>를 찾고,
|
||||
// parameter body는 opaque string으로 둔다.
|
||||
}
|
||||
```
|
||||
|
||||
구현 세부:
|
||||
|
||||
- `<tool_call>` open/close와 `<function=name>` open/close는 tag boundary로만 인식한다.
|
||||
- `<parameter=key>` body 내부의 `<edge-host>`, shell redirect `<`, JSON string, Markdown fence는 tag로 해석하지 않는다.
|
||||
- 닫히지 않은 `<tool_call>`은 기존처럼 `malformed tool call: unclosed tool_call block`.
|
||||
- `<tool_call>` 안에 function block이 없으면 기존 error string `malformed tool call: missing function block or definition inside tool_call`를 유지한다.
|
||||
- unknown tool은 기존 `tool %q is not in request tools`를 유지한다.
|
||||
- `collectXMLCandidateMatches`와 `collectXMLTextToolCallMatches`가 같은 scanner 결과를 사용하게 한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/types.go`: XML regex block/function/parameter parser를 scanner helper로 교체.
|
||||
- [ ] `apps/edge/internal/openai/types.go`: `collectXMLCandidateMatches`와 `collectXMLTextToolCallMatches`가 parser 결과를 공유.
|
||||
- [ ] `apps/edge/internal/openai/types.go`: existing error string과 return semantics 유지.
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: 기존 XML/mustache/unknown/malformed 테스트가 계속 통과하는지 확인.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
작성한다. BUGFIX_TOOLCALL-1 fixture가 scanner 회귀를 검증한다. 추가로 `</parameter>`가 JSON string 안에 들어가는 케이스는 이번 범위에서 필수로 만들지 않는다. 실제 실패는 `<edge-host>` literal이며, 임의 close-tag escaping 정책은 별도 parser hardening 후속으로 둔다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/edge/internal/openai -run 'TestChatCompletions(SynthesizesToolCallsFromClineTextBlock|SynthesizesTextToolCallsForProviderRoute|SynthesizesProviderTextToolCallsWhenFallbackMarked|FailsMalformedTextToolCallAfterRetryLimit|SynthesizesEditToolCallWithMarkdownAndAngleBracketPlaceholder)$'
|
||||
```
|
||||
|
||||
Expected: PASS. 기존 raw text tool-call normalization behavior와 실제 edit fixture가 모두 통과한다.
|
||||
|
||||
## BUGFIX_TOOLCALL-3 tools streaming을 buffered validation 경로로 완화
|
||||
|
||||
### 문제
|
||||
|
||||
live SSE 경로는 `apps/edge/internal/openai/stream.go:22`에서 `outputPolicy.Strict && outputPolicy.StreamBuffer`일 때만 buffered retry 경로로 간다. 그렇지 않으면 `stream.go:217`-`220`에서 complete 시점에 raw text tool-call 합성을 시도하고 실패하면 SSE `tool_validation_error`를 낸다. 이 경로는 이미 content delta를 보냈을 수 있어 bounded retry가 불가능하다. `tool_validation.go:45`-`50`도 live SSE의 retry 제한을 설명한다.
|
||||
|
||||
```go
|
||||
// apps/edge/internal/openai/stream.go:36
|
||||
if outputPolicy.Strict && outputPolicy.StreamBuffer {
|
||||
s.streamBufferedChatCompletion(...)
|
||||
}
|
||||
|
||||
// apps/edge/internal/openai/stream.go:217
|
||||
} else if len(req.Tools) > 0 {
|
||||
res := synthesizeToolCallsFromTextResult(...)
|
||||
if res.validationErr != nil {
|
||||
writeSSEErrorWithType(...)
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
이번 dev failure는 `strict_output=false`, `strict_stream_buffer=false`인 streaming tool-use turn에서 발생해 재시도 없이 client-visible error가 되었다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`tools[]`가 있는 chat completions streaming 요청은 strict output 여부와 무관하게 buffered validation 경로로 보낸다.
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
if outputPolicy.Strict && outputPolicy.StreamBuffer || len(req.Tools) > 0 {
|
||||
s.streamBufferedChatCompletion(w, r, req, submitReq, handle, flusher, outputPolicy, validation)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
동시에 `toolValidationContractForChatRequest`도 `req.Stream && len(req.Tools)>0` buffered policy와 일치하도록 validation contract를 활성화한다. live SSE에서 raw tool-call filter를 유지하는 기존 방어는 no-tools streaming과 혹시 남는 live path용으로 보존한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/edge/internal/openai/stream.go`: `len(req.Tools)>0` stream은 `streamBufferedChatCompletion`을 사용.
|
||||
- [ ] `apps/edge/internal/openai/tool_validation.go`: stream+tools validation contract가 buffered path에서 활성화되도록 조건 정렬.
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: 기존 `TestChatCompletionsLiveStreamSkipsRuntimeToolValidation` 기대값을 새 정책에 맞게 수정하거나 새 테스트로 대체.
|
||||
- [ ] `apps/edge/internal/openai/server_test.go`: invalid first stream attempt -> valid retry -> only valid tool_call emitted을 strict flag 없이 검증.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
작성한다. Test name:
|
||||
|
||||
- `TestChatCompletionsToolsStreamRetriesMalformedToolCallBeforeChunk`
|
||||
|
||||
목표:
|
||||
|
||||
- config `StrictOutput:false`, `StrictStreamBuffer:false`.
|
||||
- request `stream:true`, `tools[]`.
|
||||
- first fake run malformed native/text tool-call, second run valid.
|
||||
- `fake.reqsSnapshot()` length 2.
|
||||
- SSE body contains valid second call and `[DONE]`, first bad call/raw `<tool_call>` does not leak.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/edge/internal/openai -run 'TestChatCompletions(ToolsStreamRetriesMalformedToolCallBeforeChunk|StrictBufferedStreamRetriesMalformedToolCallBeforeChunk|ProviderStreamBlocksMalformedTextToolCall)$'
|
||||
```
|
||||
|
||||
Expected: PASS. tool-bearing stream retry policy works without requiring `StrictOutput:true`.
|
||||
|
||||
## BUGFIX_TOOLCALL-4 검증과 dev smoke 계약
|
||||
|
||||
### 문제
|
||||
|
||||
OpenAI-compatible raw text tool-call boundary는 외부 API 계약이다. local unit regression만으로 parser는 검증 가능하지만, dev-runtime `18083` smoke는 원격 checkout과 binary가 변경 commit을 반영해야 의미가 있다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
local regression을 필수로 실행하고, dev-runtime이 구현 commit으로 배포된 뒤 non-stream/stream smoke를 수행한다. dev smoke payload는 tracked 파일에 secret을 남기지 않고 `/tmp`에서 생성한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `CODE_REVIEW-local-G06.md`: local test stdout/stderr 붙여넣기.
|
||||
- [ ] `CODE_REVIEW-local-G06.md`: dev smoke를 수행했으면 `/tmp` output path와 PASS 조건 기록.
|
||||
- [ ] dev smoke를 수행하지 못하면 원격 sync/binary stale/token 부재 중 정확한 blocker를 기록.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
추가 테스트 파일은 만들지 않는다. `server_test.go` 회귀 테스트가 구현 검증이다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/edge/internal/openai/server_test.go` | BUGFIX_TOOLCALL-1, BUGFIX_TOOLCALL-3 |
|
||||
| `apps/edge/internal/openai/types.go` | BUGFIX_TOOLCALL-2 |
|
||||
| `apps/edge/internal/openai/stream.go` | BUGFIX_TOOLCALL-3 |
|
||||
| `apps/edge/internal/openai/tool_validation.go` | BUGFIX_TOOLCALL-3 |
|
||||
| `agent-task/openai_text_tool_call_boundary/CODE_REVIEW-local-G06.md` | BUGFIX_TOOLCALL-4 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/edge/internal/openai
|
||||
```
|
||||
|
||||
Expected: PASS. Go test cache output is acceptable only if the new test names appear in the package test list during implementation; if in doubt run `go test -count=1 ./apps/edge/internal/openai`.
|
||||
|
||||
Dev-runtime smoke after the implementation commit is deployed to `/Users/toki/agent-work/iop-dev`:
|
||||
|
||||
```bash
|
||||
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && git status --branch --short && git log --oneline -1 && test -x build/dev-runtime/bin/edge && build/dev-runtime/bin/edge --config build/dev-runtime/edge.yaml config refresh --help >/tmp/iop-tool-boundary-refresh-help.txt && curl -fsS http://127.0.0.1:18001/edges/edge-toki-labs-dev/status >/tmp/iop-tool-boundary-status.json'
|
||||
```
|
||||
|
||||
Expected: remote checkout is the implementation commit or documented deployed commit, edge binary exists, refresh help succeeds, status JSON exists. If the checkout/binary is stale, do not treat the following smoke as current-change verification.
|
||||
|
||||
```bash
|
||||
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_OPENAI_TOKEN:-}" && cat >/tmp/iop-tool-boundary-nonstream.json <<'"'"'JSON'"'"'
|
||||
{"model":"qwen3.6:35b","stream":false,"messages":[{"role":"user","content":"Reply with exactly one edit tool call for /tmp/example.md that writes a curl example using http://<edge-host>:18081/v1/chat/completions inside a markdown bash fence."}],"tools":[{"type":"function","function":{"name":"edit","parameters":{"type":"object","properties":{"path":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"newText":{"type":"string"}},"required":["newText"]}}},"required":["path","edits"],"additionalProperties":false}}}],"tool_choice":"auto"}
|
||||
JSON
|
||||
curl -sS http://127.0.0.1:18083/v1/chat/completions -H "Authorization: Bearer ${IOP_OPENAI_TOKEN}" -H "Content-Type: application/json" --data-binary @/tmp/iop-tool-boundary-nonstream.json >/tmp/iop-tool-boundary-nonstream.out
|
||||
python3 - <<'"'"'PY'"'"'
|
||||
from pathlib import Path
|
||||
s=Path("/tmp/iop-tool-boundary-nonstream.out").read_text()
|
||||
assert "<tool_call" not in s and "\\u003ctool_call" not in s, s
|
||||
assert "tool_calls" in s or "tool_validation_error" in s, s
|
||||
PY'
|
||||
```
|
||||
|
||||
Expected: no raw `<tool_call>` leak. A valid raw text tool-call is normalized to `tool_calls`; if the live model refuses or emits malformed output after retry, response must be controlled `tool_validation_error`, not leaked success content.
|
||||
|
||||
```bash
|
||||
ssh toki@toki-labs.com 'cd /Users/toki/agent-work/iop-dev && test -n "${IOP_OPENAI_TOKEN:-}" && python3 - <<'"'"'PY'"'"' >/tmp/iop-tool-boundary-stream.json
|
||||
import json
|
||||
print(json.dumps({"model":"qwen3.6:35b","stream":True,"messages":[{"role":"user","content":"Reply with exactly one edit tool call for /tmp/example.md that writes a curl example using http://<edge-host>:18081/v1/chat/completions inside a markdown bash fence."}],"tools":[{"type":"function","function":{"name":"edit","parameters":{"type":"object","properties":{"path":{"type":"string"},"edits":{"type":"array","items":{"type":"object","properties":{"newText":{"type":"string"}},"required":["newText"]}}},"required":["path","edits"],"additionalProperties":False}}}],"tool_choice":"auto"}))
|
||||
PY
|
||||
curl -sS http://127.0.0.1:18083/v1/chat/completions -H "Authorization: Bearer ${IOP_OPENAI_TOKEN}" -H "Content-Type: application/json" --data-binary @/tmp/iop-tool-boundary-stream.json >/tmp/iop-tool-boundary-stream.sse
|
||||
python3 - <<'"'"'PY'"'"'
|
||||
from pathlib import Path
|
||||
s=Path("/tmp/iop-tool-boundary-stream.sse").read_text()
|
||||
assert "<tool_call" not in s and "\\u003ctool_call" not in s, s
|
||||
assert "data: [DONE]" in s, s
|
||||
assert "tool_calls" in s or "tool_validation_error" in s, s
|
||||
PY'
|
||||
```
|
||||
|
||||
Expected: SSE terminates with `[DONE]`, no raw `<tool_call>` leak, and either normalized `delta.tool_calls` or controlled `tool_validation_error`.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<!-- task=review_followup_alignment/02_docs_runtime_context_split plan=0 tag=REVIEW_DOCS -->
|
||||
# CODE_REVIEW-local-G02: 공개 문서와 로컬 검증 문맥 분리
|
||||
date=2026-07-06
|
||||
|
||||
## 상태
|
||||
구현 대기
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
구현 후 이 파일의 구현 에이전트 소유 섹션을 실제 변경 내용, 계획 대비 변경, 검증 출력으로 채운다. active `PLAN-*.md`와 `CODE_REVIEW-*.md`는 남겨두고 review 준비 상태를 보고한다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`를 만들거나 archive/complete 처리를 하지 않는다.
|
||||
|
||||
## 구현 체크리스트
|
||||
- [ ] REVIEW_DOCS-1의 공개 문서 축소와 local test 문맥 이동을 수행한다.
|
||||
- [ ] 중간 검증 명령을 실행하고 결과를 `CODE_REVIEW-local-G02.md`에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 archive 로그로 이동한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 archive 로그로 이동한다.
|
||||
- [ ] `.gitignore`의 Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent 정리 여부를 확인한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 연결 대상: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
- tracked `docs/`가 공개 가이드 성격으로 정리되었는가.
|
||||
- private/local-only 검증 문맥이 local test 문서로 이동했는가.
|
||||
- 공개 문서에 secret 원문, private IP, machine-specific host가 남지 않는가.
|
||||
|
||||
## 검증 결과
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
|
||||
### REVIEW_DOCS-1 중간 검증
|
||||
```bash
|
||||
rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md
|
||||
rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
rg --sort path -n '192\.168\.|dev-runtime|edge-toki-labs|Windows SSH|toki-labs\.com' docs/edge-local-dev-guide.md
|
||||
rg --sort path -n 'mattermost_credentials|Bearer [A-Za-z0-9._-]+' docs/edge-local-dev-guide.md
|
||||
```
|
||||
|
||||
## 코드리뷰 결과
|
||||
_리뷰 에이전트가 append한다._
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
# Edge Local Quickstart
|
||||
|
||||
이 문서는 `toki-labs.com` Control Plane에 native Edge를 붙이고, Ollama Node를 연결해 OpenAI-compatible smoke까지 확인하는 최소 절차다.
|
||||
이 문서는 Edge를 로컬에서 빌드하고 실행해 Node를 연결하고, OpenAI-compatible smoke까지 확인하는 최소 절차다.
|
||||
|
||||
설정 기준은 `edge.yaml`이다. 주소는 환경 변수로 흩뿌리지 않는다.
|
||||
|
||||
## 1. Package
|
||||
|
||||
현재 `toki-labs.com` 원격처럼 Apple Silicon macOS에서 실행한다.
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
|
@ -17,8 +15,8 @@ make build
|
|||
```bash
|
||||
rm -rf "$HOME/iop-edge-test"
|
||||
mkdir -p "$HOME/iop-edge-test"
|
||||
tar -xzf build/packages/iop-edge-darwin-arm64.tar.gz -C "$HOME/iop-edge-test"
|
||||
cd "$HOME/iop-edge-test/iop-edge-darwin-arm64"
|
||||
tar -xzf build/packages/iop-edge-<platform>.tar.gz -C "$HOME/iop-edge-test"
|
||||
cd "$HOME/iop-edge-test/iop-edge-<platform>"
|
||||
./iop-edge config init
|
||||
```
|
||||
|
||||
|
|
@ -37,22 +35,16 @@ models:
|
|||
ollama-gemma: "gemma4:26b"
|
||||
|
||||
edge:
|
||||
id: "edge-toki-labs"
|
||||
name: "Toki Labs Edge"
|
||||
id: "edge-1"
|
||||
name: "Edge 1"
|
||||
|
||||
server:
|
||||
listen: "0.0.0.0:19090"
|
||||
advertise_host: "toki-labs.com"
|
||||
|
||||
bootstrap:
|
||||
listen: "0.0.0.0:18080"
|
||||
artifact_base_url: "http://toki-labs.com:18080"
|
||||
artifact_dir: "artifacts"
|
||||
|
||||
control_plane:
|
||||
enabled: true
|
||||
wire_addr: "toki-labs.com:19081"
|
||||
|
||||
refresh:
|
||||
enabled: true
|
||||
listen: "127.0.0.1:19093"
|
||||
|
|
@ -108,7 +100,8 @@ echo $! > run/iop-edge.pid
|
|||
확인:
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18000/edges
|
||||
# Control Plane에 연결된 Edge 상태 조회
|
||||
curl -fsS http://<control-plane-host>:18000/edges
|
||||
```
|
||||
|
||||
## 5. Node 실행
|
||||
|
|
@ -118,216 +111,62 @@ curl -fsS http://toki-labs.com:18000/edges
|
|||
Node 연결 확인:
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18000/edges/edge-toki-labs/status
|
||||
curl -fsS http://<control-plane-host>:18000/edges/<edge-id>/status
|
||||
```
|
||||
|
||||
## 6. dev-runtime provider pool 반복 기준
|
||||
|
||||
현재 GX10 vLLM, OneXPlayer Lemonade, Mac MLX vLLM을 같은 model alias로 묶어 검증하는 dev-runtime 기준은 원격 runner의 동기화된 iop checkout과 `build/dev-runtime/edge.yaml`이다.
|
||||
|
||||
배포 전 원격 runner checkout은 보존 대상이 아니다. 항상 clean sync 후 dev-runtime binary를 다시 빌드한다.
|
||||
## 6. 기본 Smoke
|
||||
|
||||
```bash
|
||||
cd /Users/toki/agent-work/iop-dev
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
git clean -fd
|
||||
|
||||
go build -trimpath -o build/dev-runtime/bin/edge ./apps/edge/cmd/edge
|
||||
go build -trimpath -o build/dev-runtime/bin/iop-node ./apps/node/cmd/node
|
||||
GOOS=linux GOARCH=arm64 go build -trimpath -o build/dev-runtime/bin/iop-node-linux-arm64 ./apps/node/cmd/node
|
||||
GOOS=windows GOARCH=amd64 go build -trimpath -o build/dev-runtime/bin/iop-node-windows-amd64.exe ./apps/node/cmd/node
|
||||
```
|
||||
|
||||
기준 주소:
|
||||
|
||||
- Control Plane status: 원격 host의 `http://127.0.0.1:18001`
|
||||
- dev-runtime Edge id: `edge-toki-labs-dev`
|
||||
- bootstrap HTTP: `http://toki-labs.com:18082`
|
||||
- Edge OpenAI-compatible base URL: `http://toki-labs.com:18083/v1`
|
||||
- Edge-Node TCP transport: `toki-labs.com:18084`
|
||||
|
||||
외부로 노출되는 OpenAI-compatible API는 `openai.bearer_token`으로 보호한다. tracked 문서에는 실제 token 원문을 기록하지 않고, Cline 같은 외부 client에는 운영자가 전달한 API key를 `Authorization: Bearer ...` 형태로 설정한다.
|
||||
|
||||
기준 provider pool:
|
||||
|
||||
- model alias: `qwen3.6:35b`
|
||||
- provider candidate: `gx10-vllm` on `gx10-vllm-node`, capacity `4`, priority `0`
|
||||
- provider candidate: `onexplayer-lemonade` on `onexplayer-lemonade-node`, capacity `3`, priority `1`
|
||||
- provider candidate: `mac-mlx-vllm` on `mac-codex-node`, capacity `2`, priority `2`
|
||||
|
||||
Provider-pool dispatch는 capacity gate를 먼저 적용해 `in_flight >= capacity`인 provider를 후보에서 제외한다. 남은 후보 중 가장 낮은 `in_flight` 레벨을 우선 선택하며, 같은 `in_flight` 레벨 안에서 priority 숫자가 낮은 provider를 먼저 선택한다. 이 기준에서 10개 동시 요청은 정상적으로 `gx10-vllm=4`, `onexplayer-lemonade=3`, `mac-mlx-vllm=2`, queue `1` 구성이 된다.
|
||||
|
||||
GX10 vLLM은 `nvidia/Qwen3.6-35B-A3B-NVFP4`를 Docker container `iop-vllm-qwen36-prev-20260704063128`로 띄운다. dev 기준 endpoint는 `http://192.168.0.91:8001/v1`, runtime baseline은 `--max-model-len 262144`, `--max-num-seqs 4`, `--gpu-memory-utilization 0.30`, `--reasoning-parser qwen3`, `--default-chat-template-kwargs '{"enable_thinking":true}'`이다.
|
||||
|
||||
OneXPlayer Lemonade Node는 원격 runner나 Edge host에서 다시 SSH하거나 proxy process로 띄우지 않는다. 현재 작업 호스트에서 Windows host에 `ssh r0bin@192.168.0.59`로 직접 접속한 뒤, 그 host 안에서 generated PowerShell bootstrap 명령을 실행한다. 이때 Node 작업 경로는 `$HOME\iop-field`이며, Lemonade provider endpoint는 Windows host 로컬에서 접근 가능한 값을 기준으로 검증한다.
|
||||
|
||||
OneXPlayer Lemonade는 `Qwen3.6-35B-A3B-MTP-GGUF` artifact를 사용하되 runtime MTP speculative decoding은 끈다. dev long-context 기준 load 설정은 Vulkan backend, `ctx_size=524288`, `llamacpp_args="--spec-type none -np 3 -cb -fa on -b 4096 -ub 1024"`로 고정한다. llama.cpp server 기준 `ctx_size`는 전체 KV/context 예산이고 `-np`는 server slot 수이므로, 이 설정은 총 context 예산을 full `262144` window 2개분으로 제한하고 slot 3개를 둔다. 2026-07-04 관측 기준 `/slots`는 slot별 `n_ctx=174848`을 반환한다. 재시작 뒤에도 유지되도록 `/v1/load`는 `save_options=true`로 적용한다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://192.168.0.59:13305/v1/load \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model_name":"Qwen3.6-35B-A3B-MTP-GGUF","ctx_size":524288,"llamacpp_backend":"vulkan","llamacpp_args":"--spec-type none -np 3 -cb -fa on -b 4096 -ub 1024","save_options":true}'
|
||||
```
|
||||
|
||||
Mac MLX provider는 Edge host 자신에서 `vllm-mlx`로 띄운다. dev 기준 endpoint는 `http://127.0.0.1:8002/v1`, served model은 `mlx-community/Qwen3.6-35B-A3B-4bit`, provider capacity는 `2`이다. 작업 경로는 `/Users/toki/agent-work/iop-mlx-vllm`, Python 환경은 `.venv`의 Homebrew Python 3.12, 모델 cache는 `hf-cache`를 사용한다. 확인된 package baseline은 `vllm-mlx 0.3.0`, `mlx 0.31.2`, `mlx-lm 0.31.3`이다.
|
||||
|
||||
vllm-mlx process는 `--max-num-seqs 2`, `--max-kv-size 262144`, `--max-request-tokens 262144`, `--use-paged-cache --paged-cache-block-size 64 --max-cache-blocks 4096`로 고정한다. 이 설정은 병렬 슬롯을 `2`로 제한하고, KV/context 예산을 full `262144` window 1개분으로 맞춘다.
|
||||
|
||||
```bash
|
||||
cd /Users/toki/agent-work/iop-mlx-vllm
|
||||
export HF_HOME="$PWD/hf-cache"
|
||||
export IOP_MLX_API_KEY="<edge.yaml local bearer token>"
|
||||
nohup .venv/bin/vllm-mlx serve mlx-community/Qwen3.6-35B-A3B-4bit \
|
||||
--offline \
|
||||
--host 127.0.0.1 \
|
||||
--port 8002 \
|
||||
--api-key "$IOP_MLX_API_KEY" \
|
||||
--max-num-seqs 2 \
|
||||
--max-kv-size 262144 \
|
||||
--max-request-tokens 262144 \
|
||||
--use-paged-cache \
|
||||
--paged-cache-block-size 64 \
|
||||
--max-cache-blocks 4096 \
|
||||
--enable-auto-tool-choice \
|
||||
--tool-call-parser qwen \
|
||||
--reasoning-parser qwen3 \
|
||||
--default-chat-template-kwargs '{"enable_thinking": true}' \
|
||||
--mllm \
|
||||
> logs/vllm-mlx.stdout.log 2> logs/vllm-mlx.stderr.log &
|
||||
echo $! > vllm-mlx.pid
|
||||
```
|
||||
|
||||
Mac MLX provider의 주요 운영 파일은 `vllm-mlx.pid`, `logs/vllm-mlx.stdout.log`, `logs/vllm-mlx.stderr.log`다. 2026-06-24 직접 호출 측정 기준으로 `max_tokens=192`, `enable_thinking=false`에서 1/2/3 동시 총합은 약 `59.66`, `87.31`, `98.13 tok/s`였다.
|
||||
|
||||
SSH 세션 안의 `Start-Process`는 세션 종료와 함께 `iop-node.exe`가 정리될 수 있다. 반복 배포에서는 Windows host에서 `Win32_Process.Create` 방식으로 세션 독립 실행한다.
|
||||
|
||||
```powershell
|
||||
$work = "$HOME\iop-field"
|
||||
$cmd = 'cmd.exe /c "cd /d C:\Users\r0bin\iop-field && iop-node.exe --config node.yaml serve >> iop-node.wmi.stdout.log 2>> iop-node.wmi.stderr.log"'
|
||||
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = $cmd; CurrentDirectory = $work }
|
||||
```
|
||||
|
||||
capacity, provider mapping, model alias 변경은 후보 `edge.yaml` 수정 후 `config check`, refresh dry-run, refresh apply 순서로 반영한다. `config refresh` subcommand나 `19093` admin port가 없으면 dev-runtime binary rebuild 누락으로 보고 clean sync/rebuild부터 다시 수행한다.
|
||||
|
||||
dev-runtime 번들은 바이너리를 `build/dev-runtime/bin/edge`에 둔다. 명령은 그 경로를 `EDGE_BIN`으로 잡고 실행한다.
|
||||
|
||||
```bash
|
||||
EDGE_BIN=build/dev-runtime/bin/edge
|
||||
"$EDGE_BIN" --config build/dev-runtime/edge.yaml config check
|
||||
"$EDGE_BIN" --config build/dev-runtime/edge.yaml config refresh --mode dry-run --addr 127.0.0.1:19093
|
||||
"$EDGE_BIN" --config build/dev-runtime/edge.yaml config refresh --mode apply --addr 127.0.0.1:19093
|
||||
```
|
||||
|
||||
Node host OS 재부팅은 필요하지 않다. Edge process 재시작이나 일시 단절은 Node reconnect 정책으로 처리한다. Node는 최초 연결 성공 이후 기본 `10s` 간격으로 최대 `10`회 재접속을 시도하므로, retry window 안에서 Edge가 회복되면 bootstrap 명령을 다시 실행하지 않는다. retry 한계를 넘겨 Node process가 종료된 경우에만 해당 host에서 새 bootstrap 실행이 필요하다.
|
||||
|
||||
반복 검증 순서:
|
||||
|
||||
1. Control Plane status에서 `edge-toki-labs-dev`의 connected node 수와 node id를 확인한다.
|
||||
2. Edge host에서 `toki-labs.com:18084`의 established node TCP connection 수를 확인한다.
|
||||
3. Edge process를 재시작하고 bootstrap 재실행 없이 3개 Node 연결이 회복되는지 확인한다.
|
||||
4. `http://toki-labs.com:18083/healthz`와 `/v1/models`를 확인한다.
|
||||
5. capacity를 `gx10-vllm=4`, `onexplayer-lemonade=3`, `mac-mlx-vllm=2`로 맞춘 후보 config를 refresh apply하고 Edge process가 유지되는지 확인한다. Node adapter capacity 변경이 `restart_required`로 반환되면 Edge process를 새 config로 재시작한 뒤 Node reconnect 상태를 확인한다.
|
||||
6. `qwen3.6:35b`로 `/v1/responses`와 `/v1/chat/completions` 각각에 provider capacity 총합 + 1개 동시 요청을 보낸다.
|
||||
7. capacity `gx10-vllm=4`, `onexplayer-lemonade=3`, `mac-mlx-vllm=2` 기준이면 endpoint별 9개 동시 요청에서 Control Plane status의 provider snapshot이 총 `in_flight=9`, `queued=0`으로 처리되어야 한다. 10개 동시 요청에서는 `in_flight=9`, `queued>=1`, 13개 동시 확장 smoke에서는 `in_flight=9`, `queued>=4`를 기대한다.
|
||||
8. 요청 완료 후 provider snapshot이 `in_flight=0`, `queued=0`으로 회복되는지 확인한다.
|
||||
|
||||
2026-06-24 dev 13-way 확장 smoke는 Mac capacity가 `3`이던 이전 관측이다. 해당 run에서는 `/v1/chat/completions` 13개 요청이 모두 성공했고 peak `in_flight=10`, 실제 queue 대기 `3`개가 관측되었다. 현재 Mac capacity `2` 기준의 13-way smoke 기대치는 peak `in_flight=9`, queue 대기 `4`개 이상이다.
|
||||
|
||||
Qwen 계열 thinking 모델은 reasoning 텍스트를 포함해 응답할 수 있다. 이 dev smoke에서는 thinking 출력을 실패로 보지 않고, HTTP 성공, final marker 포함, provider log/run count 증가를 기준으로 판정한다.
|
||||
|
||||
### Request-level think-control smoke
|
||||
|
||||
`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다. dev-runtime provider pool에서는 vLLM, vLLM-MLX, Lemonade 지원 범위가 다르므로 provider-isolated smoke로 provider별 기대 결과를 분리해 판정한다.
|
||||
|
||||
- 공통 안정 smoke: `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget`
|
||||
- vLLM / Lemonade `think=false`: `chat_template_kwargs.enable_thinking=false`로 reasoning 생성을 끄고 content stream이 유지되어야 한다.
|
||||
- vLLM-MLX `think=false` 또는 `reasoning_effort=none`: 현재 런타임은 streaming reasoning disable을 지원하지 않으므로 `unsupported think control` 오류를 기대한다.
|
||||
- vLLM / vLLM-MLX / Lemonade `reasoning_effort=low|medium|high`: 현재 provider 매핑에서는 `unsupported think control` 오류를 반환한다.
|
||||
|
||||
예시 (thinking 비활성화, provider-isolated vLLM 또는 Lemonade 기대):
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <token>' \
|
||||
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"think":false}'
|
||||
```
|
||||
|
||||
예시 (thinking 생성은 허용하고 response reasoning_content만 숨김):
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <token>' \
|
||||
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
|
||||
```
|
||||
|
||||
예시 (budget-only):
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <token>' \
|
||||
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"hello"}],"thinking_token_budget":512}'
|
||||
```
|
||||
|
||||
`reasoning_effort=low` 같은 effort smoke는 현재 dev-runtime provider 매핑에서 unsupported error를 기대한다. provider pool에 vLLM/vLLM-MLX/Lemonade가 섞여 있으면 어떤 provider로 라우팅되더라도 성공 stream으로 판정하지 않는다.
|
||||
|
||||
현재 공개 API는 개별 request의 최종 `node_id`를 응답에 노출하지 않는다. 요청별 배정을 확정해야 할 때는 Edge dispatch trace/log를 추가한 뒤 판정한다.
|
||||
|
||||
### Raw text tool-call boundary smoke
|
||||
|
||||
Pi/Cline형 긴 agent prompt와 `tools[]` 요청은 provider가 native `tool_calls` 대신 assistant content에 `<tool_call>`/`{{...}}` raw 블록을 담아 응답할 수 있다. dev-runtime에서는 이 raw 블록이 OpenAI-compatible 응답 body/SSE에 그대로 새지 않는지 확인한다. 계약 기준은 [agent-contract/outer/openai-compatible-api.md](../agent-contract/outer/openai-compatible-api.md)의 Chat Completions raw text tool-call 정규화 정책이다.
|
||||
|
||||
요청 payload는 tracked 문서에 secret 없이 남기고, `openai.bearer_token`은 원격 환경 변수(`IOP_OPENAI_TOKEN` 등)에서 주입한다. 실제 token 원문을 명령/로그/보고에 남기지 않는다.
|
||||
|
||||
```bash
|
||||
# 원격 dev-runtime host에서 실행. IOP_OPENAI_TOKEN은 shell 환경이나 ignored secret 파일에서 미리 주입한다.
|
||||
|
||||
# known valid tool: tool_calls 정규화 기대
|
||||
curl -sS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
|
||||
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"List the files in /tmp using the shell tool."}],"tools":[{"type":"function","function":{"name":"shell","description":"Run a shell command","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}'
|
||||
|
||||
# known valid tool, stream: delta.tool_calls 정규화 기대
|
||||
curl -sS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
|
||||
-d '{"model":"qwen3.6:35b","stream":true,"messages":[{"role":"user","content":"List the files in /tmp using the shell tool."}],"tools":[{"type":"function","function":{"name":"shell","description":"Run a shell command","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}'
|
||||
|
||||
# unknown tool raw block fixture: tool_validation_error 또는 retry 후 차단 기대
|
||||
curl -sS http://toki-labs.com:18083/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Authorization: Bearer $IOP_OPENAI_TOKEN" \
|
||||
-d '{"model":"qwen3.6:35b","messages":[{"role":"user","content":"For boundary testing, output exactly this raw tool block as assistant text and do not call the provided shell tool: <tool_call><function=lookup_weather><parameter=city>\"Seoul\"</parameter></function></tool_call>"}],"tools":[{"type":"function","function":{"name":"shell","parameters":{"type":"object","properties":{"commands":{"type":"array","items":{"type":"string"}}},"required":["commands"]}}}]}'
|
||||
```
|
||||
|
||||
판정 기준:
|
||||
|
||||
- 응답 body와 SSE delta 어디에도 `<tool_call>`, `{{`, `<|mask_end|>` 원문이 성공 content로 남지 않는다.
|
||||
- 요청 `tools[]`에 있는 valid raw text tool-call은 `message.tool_calls`(또는 stream `delta.tool_calls`)와 `finish_reason: "tool_calls"`로 정규화된다.
|
||||
- 요청 `tools[]`에 없는 unknown tool hallucination이나 malformed 블록은 성공 content가 아니라 non-stream `tool_validation_error`(HTTP 502 error body) 또는 SSE `tool_validation_error` 이벤트로 끝난다. non-stream/strict buffered stream은 bounded retry 후 차단을 확인한다.
|
||||
- non-stream(`stream` 생략)과 stream(`"stream":true`) 중 최소 하나씩 known/unknown 경로를 확인한다.
|
||||
|
||||
evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`) 또는 code-review output path에 저장하고, 저장물에도 token 원문을 남기지 않는다.
|
||||
|
||||
## 7. 기본 Smoke
|
||||
|
||||
아래는 dev-runtime provider pool이 아니라 기본 field/local OpenAI-compatible profile 기준 예시다. dev-runtime provider pool 검증은 위의 `18083`/`qwen3.6:35b` 기준을 따른다.
|
||||
|
||||
```bash
|
||||
curl -fsS http://toki-labs.com:18081/v1/models
|
||||
curl -fsS http://<edge-host>:18081/v1/models
|
||||
|
||||
./iop-edge --config edge.yaml smoke openai \
|
||||
--model gemma4:26b \
|
||||
--base-url http://toki-labs.com:18081 \
|
||||
--base-url http://<edge-host>:18081 \
|
||||
--timeout 60s
|
||||
```
|
||||
|
||||
외부 OpenAI-compatible client base URL:
|
||||
|
||||
```text
|
||||
http://toki-labs.com:18081/v1
|
||||
http://<edge-host>:18081/v1
|
||||
```
|
||||
|
||||
## 7. Thinking/Reasoning 제어 smoke
|
||||
|
||||
`/v1/chat/completions` 요청은 `think`, `reasoning_effort`, `thinking_token_budget`, `include_reasoning`으로 thinking/reasoning 동작과 응답 노출을 제어할 수 있다.
|
||||
|
||||
- 공통 안정 smoke: `think` 생략, `include_reasoning=false`, 또는 budget-only `thinking_token_budget`
|
||||
- `think=false`: reasoning 생성을 끄고 content stream이 유지되어야 한다.
|
||||
- `include_reasoning=false`: thinking 생성은 허용하고 response reasoning_content만 숨김.
|
||||
- `reasoning_effort=low|medium|high`: provider 매핑에 따라 unsupported error를 반환할 수 있다.
|
||||
|
||||
예시 (thinking 비활성화):
|
||||
|
||||
```bash
|
||||
curl -fsS http://<edge-host>:18081/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <token>' \
|
||||
-d '{"model":"<model-alias>","messages":[{"role":"user","content":"hello"}],"think":false}'
|
||||
```
|
||||
|
||||
예시 (thinking 생성은 허용하고 response reasoning_content만 숨김):
|
||||
|
||||
```bash
|
||||
curl -fsS http://<edge-host>:18081/v1/chat/completions \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"model":"<model-alias>","messages":[{"role":"user","content":"hello"}],"include_reasoning":false}'
|
||||
```
|
||||
|
||||
## 8. Raw text tool-call boundary smoke
|
||||
|
||||
Pi/Cline형 긴 agent prompt와 `tools[]` 요청은 provider가 native `tool_calls` 대신 assistant content에 raw 블록을 담아 응답할 수 있다. 계약 기준은 `agent-contract/outer/openai-compatible-api.md`의 Chat Completions raw text tool-call 정규화 정책이다.
|
||||
|
||||
요청 payload는 tracked 문서에 secret 없이 남기고, token은 원격 환경 변수에서 주입한다. 실제 token 원문을 명령/로그/보고에 남기지 않는다.
|
||||
|
||||
판정 기준:
|
||||
|
||||
- 응답 body와 SSE delta 어디에도 raw tool block 원문이 성공 content로 남지 않는다.
|
||||
- 요청 `tools[]`에 있는 valid tool-call은 `message.tool_calls` 또는 stream `delta.tool_calls`와 `finish_reason: "tool_calls"`로 정규화된다.
|
||||
- 요청 `tools[]`에 없는 unknown tool hallucination이나 malformed 블록은 success content가 아니라 `tool_validation_error`로 끝난다.
|
||||
|
||||
evidence는 tracked 문서가 아니라 ignored run 위치(`agent-test/runs/**`) 또는 code-review output path에 저장한다.
|
||||
Loading…
Reference in a new issue