docs(handoff): 스트리밍 세션 정지 진단을 인계한다

This commit is contained in:
toki 2026-08-13 09:16:34 +09:00
parent b205433a32
commit c578442a6c
2 changed files with 214 additions and 197 deletions

View file

@ -0,0 +1,214 @@
---
handoff_version: 1
handoff_status: implementation-not-started
created_at: 2026-08-13
target: dev OpenAI-compatible ornith:35b streaming session stall
local_source_revision: b205433a323e6fd47ceb55998d72a624d432887e
observed_dev_revision: fd32abb4b6b15037c24be01821b430a960afd967
---
# Handoff: `ornith:35b` streaming session stall과 terminal 지연
## 현재 타겟
- 증상: Chronos `agent-task`에서 Pi `iop/ornith:35b` worker가 `failed:session-stall:143`으로 세 번 연속 종료된다.
- 범위: IOP dev OpenAI-compatible Chat Completions provider tunnel, Stream Evidence Gate, Node/provider liveness timeout 정합성.
- 상태: 진단만 수행했다. 코드, 설정, 배포는 변경하지 않았다.
- 다음 세션 첫 진입점:
1. `apps/edge/internal/openai/stream_gate_runtime.go`
2. `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
3. `apps/node/internal/adapters/openai_compat/provider_tunnel.go`
4. `packages/go/execution/liveness.go`
## 환경 정정
초기 조사에서 `dev-corp`를 열어 DGX Spark 설정을 본 것은 잘못된 환경 선택이었다. Spark 관련 해석은 이 장애의 근거로 사용하지 않는다.
실제 Pi가 호출한 dev Edge 런타임을 기준으로 다시 확인했다.
- dev에는 `gx10-vllm-node`, `onexplayer-lemonade-node`, `rtx5090-lemonade-node`가 등록돼 있다.
- 현재 배포 설정에서 `gx10-vllm``laguna-s:2.1`에 연결돼 있다.
- 현재 `ornith:35b``onexplayer-lemonade``rtx5090-lemonade`에 연결돼 있다.
- 이번 세 실패의 마지막 요청은 모두 실제 provider `onexplayer-lemonade`로 dispatch됐다.
- 제품 의도가 `ornith:35b`도 GX10에서 실행되는 것이라면, 현재 배포 route/catalog가 그 의도와 다르다. 구현 전에 운영 의도와 현재 binding을 먼저 확정해야 한다.
## 확정된 관측
Chronos dispatcher의 세 attempt는 모두 native session이 `active`이고 pending tool call이 없는 상태에서 약 180초 동안 새 출력이 없어 dispatcher가 `SIGTERM`을 보냈다. Unix exit `143`은 이 `SIGTERM`의 결과이며 provider가 직접 반환한 오류 코드가 아니다.
| Attempt | 마지막 IOP 요청 | StreamGate 관측 | Chronos 종료 |
|---|---|---|---|
| 21 | 입력 추정 `40,665` tokens, `onexplayer-lemonade` | 네 번 release 후 terminal 없음 | silence `180.048s`, dispatcher `SIGTERM` |
| 22 | 입력 추정 `40,830` tokens, `onexplayer-lemonade` | 두 번 release 후 terminal 없음 | silence `180.063s`, dispatcher `SIGTERM` |
| 23 | `40,995`-token turn은 약 `224.7s` 뒤 정상 terminal; 다음 `41,080`-token turn도 같은 provider | 다음 turn은 `response_start`만 staging되고 delta/terminal 없음 | silence `180.070s`, dispatcher `SIGTERM` |
Chronos 측 마지막 출력 시각과 IOP의 마지막 `release_committed` 시각이 밀리초 수준으로 일치한다. IOP 이후 Pi/dispatcher 구간에서 출력이 별도로 유실된 정황은 없다.
관련 Chronos evidence:
- attempt 21: `/config/workspace/chronos-s0/.git/agent-task-dispatcher/runs/20260813T074422+0900__m-chronos-product-delivery-install-update-lifecycle__01_release_metadata__p2__worker__a21/`
- attempt 22: `/config/workspace/chronos-s0/.git/agent-task-dispatcher/runs/20260813T075124+0900__m-chronos-product-delivery-install-update-lifecycle__01_release_metadata__p2__worker__a22/`
- attempt 23: `/config/workspace/chronos-s0/.git/agent-task-dispatcher/runs/20260813T075511+0900__m-chronos-product-delivery-install-update-lifecycle__01_release_metadata__p2__worker__a23/`
각 디렉터리의 `locator.json``stream.log`만 보면 dispatcher 종료 사유와 마지막 native output을 재확인할 수 있다. 이 경로들은 Chronos worktree-local evidence이므로 정리 전에 필요한 요약을 별도 보존해야 한다.
## Stream Evidence Gate 판정
dev Edge에서 `stream_evidence_gate.enabled=true`이며 다음 정책이 `ornith:35b`에만 selector로 적용된다.
- filter: `repeat_guard`
- enforcement: `blocking`
- hold: `500` Unicode runes
- filter timeout: `5000ms`
그러나 실패 시간대의 raw-free filter observation은 모두 다음과 같다.
- `openai.repeat_guard`: `pass`, `repeat_rolling_clear`
- attempt 23의 정상 완료 turn: `openai.repeat_guard.action``pass`, `repeat_action_clear`
- `violation`, `fatal`, `recover`, continuation dispatch는 없음
따라서 반복 검증 판정이 요청을 차단하거나 recovery loop에 넣은 것이 직접 원인은 아니다.
다만 500-rune rolling hold는 증상을 확대한다. 이 값은 항상 마지막 500 runes를 보류한다는 뜻이 아니다. pending이 500 runes 미만이면 평가와 release를 기다리고, 500 이상이 되면 filter를 평가해 pass 시 그 epoch의 pending 전체를 즉시 release한다. terminal trigger가 오면 500 미만이어도 평가한다. 따라서 provider가 다음 delta나 terminal trigger를 보내지 않으면 현재 sub-threshold pending tail만 caller에게 release되지 않는다. caller가 180초 후 종료되면 그 tail도 정상 terminal 없이 폐기된다.
### 종료 신호 경계와 현재 관측 공백
OpenAI-compatible stream에는 서로 다른 계층의 종료 신호가 있다.
1. `choices[].finish_reason`: 모델 생성이 끝났다는 논리적 완료 신호
2. SSE `data: [DONE]`: OpenAI stream framing의 완료 신호
3. upstream response EOF와 Node tunnel `END`: 실제 transport 완료 신호
4. StreamGate Core terminal: codec/runtime이 위 신호를 normalized terminal로 변환한 결과
현재 codec은 `finish_reason` chunk를 terminal trigger로 만들지 않고 wire-only frame으로 staging한다. `[DONE]` 또는 tunnel `END`가 와야 Core terminal 경로가 실행된다. 그러므로 `finish_reason`은 도착했지만 `[DONE]`/EOF/`END`가 지연되거나 누락되면, 500-rune 미만 pending tail과 staged finish frame이 함께 보류될 수 있다.
기존 raw-free observation으로 확인된 것은 실패 요청에서 Core terminal 및 tunnel `END`가 관측되지 않았다는 사실까지다. 실패 요청에 `finish_reason`이 먼저 도착했는지, `[DONE]`이 codec에 도착했지만 변환 중 유실됐는지는 현재 로그만으로 확정할 수 없다. 따라서 이 경계는 확정 원인이 아니라 코드상 유력한 버그 후보로 취급한다.
## 현재 원인 판정
### 확인됨
1. 요청은 Edge admission을 통과했고 provider tunnel의 response start와 일부 body를 받았다.
2. 실패한 turn에서 `repeat_guard`는 모두 pass했다.
3. 마지막 release 뒤 Node tunnel의 `END`, provider error terminal 또는 Core terminal이 오지 않았다.
4. attempt 23의 다음 turn은 response start 뒤 body 자체가 오지 않았다.
5. 세 요청 모두 약 40.7k~41.1k input-token 장문맥이고 실제 provider는 `onexplayer-lemonade`였다.
### 유력하지만 추가 분리가 필요함
`onexplayer-lemonade` backend가 장문맥 생성 중 진행을 멈췄거나, Node의 upstream HTTP reader/tunnel relay가 body 또는 EOF/END를 전달하지 못했을 가능성이 높다. 현재 Edge observation만으로는 provider backend와 Node relay 중 정확한 소유자를 단정할 수 없다.
### timeout 정합성 결함
- Chronos dispatcher session silence timeout: `180s`
- IOP Node `response_stall_timeout_ms`: 배포 설정에서 생략됨
- 생략 시 IOP 기본 response stall timeout: `300000ms`
- dev Edge OpenAI request timeout: `3600s`
따라서 Chronos가 180초에 Pi를 종료해 버리고, IOP Node의 300초 liveness watchdog은 typed `response_stalled` terminal을 만들 기회를 얻지 못한다. 이 순서 때문에 IOP의 terminal/error/recovery 경계가 동작하기 전에 외부에서 `143`으로 끝난다.
부분 출력이 이미 caller에게 commit된 attempt 21/22는 stall을 감지하더라도 exact replay 대상이 아니다. 그래도 IOP가 dispatcher보다 먼저 명시적 error terminal을 보내야 truncated open stream 대신 원인을 관측할 수 있다. attempt 23의 두 번째 turn처럼 caller-visible commit 전 stall은 alternate provider exact replay 후보가 될 수 있다.
## Pi TUI가 정상인 이유
Pi TUI가 Stream Evidence Gate를 우회하는 것은 아니다. 같은 OpenAI-compatible 경로를 사용한다.
- TUI에는 Chronos dispatcher의 180초 silence watchdog이 없다.
- 보통 fresh 또는 더 짧은 context에서 호출된다.
- 이번 stall은 결정적이지 않다. attempt 23의 첫 40,995-token turn도 같은 provider에서 정상 terminal까지 완료했다.
따라서 짧은 TUI 호출의 정상 완료는 filter 우회 증거가 아니며, 같은 약 41k context와 provider binding으로 재현해야 비교가 성립한다.
## 관련 구현 경계
- `apps/node/internal/adapters/openai_compat/provider_tunnel.go`
- upstream response body를 읽는 동안 `BODY` frame을 보내고 EOF 뒤 `END` frame을 보낸다.
- `apps/edge/internal/openai/stream_gate_runtime.go`
- tunnel frame을 normalized event로 변환하고 `END`에서 transport finish를 만든다.
- event source wait timeout은 OpenAI request timeout 기반이므로 현재 dev에서는 dispatcher보다 훨씬 늦다.
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- semantic delta에 provider wire를 연결한다.
- `finish_reason` chunk는 protocol state/wire-only frame으로 staging할 뿐 terminal trigger로 만들지 않는다.
- `[DONE]`은 terminal 경로를 실행하며, transport `END`도 runtime을 통해 terminal을 만든다.
- `apps/edge/internal/openai/stream_gate_release_sink.go`
- release payload를 flush하고 성공 terminal에서 staged terminal payload를 쓴다.
- `packages/go/execution/liveness.go`
- provider별 값이 없을 때 response stall timeout을 `300000ms`로 정규화한다.
## 다음 작업 순서
1. **route 의도 확정**
- dev에서 `ornith:35b`의 의도된 실제 장비/provider가 GX10인지 확인한다.
- GX10이 맞다면 현재 model catalog/provider binding drift를 먼저 수정 대상으로 잡는다.
2. **provider와 Node relay 분리 재현**
- 동일한 약 41k-token Chat Completions 요청을 `stream=true`로 준비한다.
- provider 직접 호출과 IOP Node tunnel 호출을 각각 수행한다.
- raw prompt/output은 저장하지 않고 아래 이벤트를 request correlation과 monotonic timestamp로 각각 기록한다.
- `response_start`, body/delta progress, 마지막 release
- `finish_reason_observed`와 모든 active choice가 finished됐는지 여부
- `done_marker_observed`
- `upstream_eof_observed`
- `tunnel_end_emitted`
- `core_terminal_emitted`와 terminal commit
- 각 종료 신호 시점의 `pending_runes`, staged finish/usage frame 수
- provider 직접 응답, Node ingress, tunnel emit, Edge codec, Core terminal의 시각을 대조해 어느 경계에서 신호가 멈췄는지 분리한다.
3. **timeout ownership 정렬**
- provider의 `response_stall_timeout_ms`를 Chronos dispatcher보다 짧게 설정하는 방안을 검토한다. 예: IOP `120s`, dispatcher는 IOP terminal/recovery가 끝날 margin을 포함해 더 길게 둔다.
- 수치는 장문맥에서 정상적으로 180초 이상 무출력 후 완료되는 사례가 있는지 측정한 뒤 확정한다.
- post-commit stall은 명시적 terminal error, pre-commit stall은 현재 ExactReplay gate에 따라 alternate provider recovery가 가능한지 각각 검증한다.
4. **회귀 테스트 추가**
- active `repeat_guard`에서 partial `BODY``END`가 오지 않는 fixture
- response start만 온 pre-commit stall과 alternate-provider recovery
- stream-open stall의 단일 SSE error terminal
- pending 500-rune tail이 있는 상태의 stall/cancel 정리
- 정상 finish chunk와 `[DONE]`/transport `END`의 exact-wire 단일 terminal
- `finish_reason`은 왔지만 `[DONE]`/EOF/`END`가 오지 않는 fixture에서 pending release와 종료 grace 동작
- `finish_reason` 뒤 usage chunk와 정상 `[DONE]`이 오는 fixture에서 wire 순서 보존 및 terminal exactly-once
5. **종료 처리 수정 방향 검토**
- 모든 active choice에 유효한 `finish_reason`이 확인되는 순간을 filter의 safe release trigger로 사용할지 검토한다.
- filter가 pass하면 sub-threshold pending content와 staged finish chunk를 release한다.
- trailing usage와 정상 `[DONE]`을 받을 수 있도록 짧고 bounded된 terminal grace를 둔다.
- grace 안에 `[DONE]`/`END`가 오지 않으면 terminal을 exactly-once로 finalize하고, 뒤늦은 중복 종료 신호는 무시한다.
- exact-wire passthrough 및 trailing usage 호환성 때문에 filter release trigger와 transport terminal commit을 같은 상태로 단순 합치지 않는다.
6. **dev live smoke**
- Pi TUI와 headless dispatcher를 같은 model, context 크기, provider binding으로 비교한다.
- terminal 도착 시간, 마지막 progress부터 terminal까지의 idle, 선택 provider만 기록한다.
- secret, request 원문, response 원문은 evidence에 남기지 않는다.
## 이번 세션에서 수행한 검증
환경:
```text
/config/.local/bin/go -> /config/opt/go/bin/go
go version go1.26.2 linux/arm64
GOROOT=/config/opt/go
```
실행 결과:
```text
go test -count=1 ./apps/edge/internal/openai \
-run 'Test(StreamGateChatConfiguredOutputFiltersCleanStreamSingleTerminal|OpenAITunnelCodecTerminalWire|StreamGateTunnelBuildRuntimeNoopPassRelaysRawBytes|DevRepeatGuardRequestDeadline)$'
PASS
go test -count=1 ./packages/go/streamgate \
-run 'Test(StreamReleaserPassThroughAndTerminal|StreamReleaserTerminalAfterFullReleaseSkipsSecondaryRelease|CommitBoundaryCommitsEmptySuccessAndTerminalOnce)$'
PASS
```
이 테스트들은 정상 terminal/release 경로가 현재 소스에서 동작함을 확인한다. 실제 장문맥 provider stall을 재현하거나 timeout 정합성을 검증한 것은 아니다.
## 작업 시 주의사항
- `dev-corp` Spark evidence를 이 장애에 재사용하지 않는다.
- `onexplayer-lemonade` 같은 runtime alias만 보고 물리 장비를 단정하지 않는다.
- filter가 원인이라고 전제하지 않는다. 현재 증거상 filter 판정은 전부 pass다.
- 반대로 filter의 500-rune hold가 보이는 truncation을 확대한다는 사실도 누락하지 않는다.
- 단순히 Chronos dispatcher timeout만 늘리면 open stream을 오래 유지할 뿐이다. IOP liveness terminal과의 순서를 함께 설계한다.
- live 설정 변경, provider 재시작, route 변경, 배포는 이번 handoff 범위에서 수행하지 않았다.

View file

@ -1,197 +0,0 @@
---
handoff_version: 1
handoff_status: final
source_revision: 3155be0e275437a8eedc1aa93497955a7d30465b
rollback_revision: 3155be0e275437a8eedc1aa93497955a7d30465b
task13_complete_log_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/complete.log
task13_complete_log_sha256: 30d86f5364f12dd9f2bd77c6d7b44c9689d06454e43860321228a5cbd5783821
pre_deletion_receipt_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/pre-deletion-transfer-receipt-v1.md
pre_deletion_receipt_sha256: bc1bf9eec498ebd8544eaf847f9f0a721436a41c9cb83c65d1aa6a5acfa460ba
pre_deletion_audit_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/pre-deletion-audit.log
pre_deletion_audit_sha256: 01be31c584e4ac16a8c92b8aae6bd9af74386b5b5596fade51d39bfb2cc39a85
task13_verifier_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/verify-pre-deletion-receipt-v1.py
task13_verifier_sha256: 0efd71c57dedd61d4c4ab59bb36b6ab66f955b6a1af866914a617aac8891dd25
original_manifest_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/iop-agent-chronos-ownership-manifest.tsv
original_manifest_sha256: d8598134dee99d96fc05a045091dd3d8932d4708d2c980c2a78c8b11825d2ccf
original_manifest_row_count: 300
original_manifest_file_count: 290
original_manifest_state_count: 10
original_manifest_retain_generic_count: 135
task03_historical_row_count: 303
task03_historical_file_count: 293
task03_historical_state_count: 10
task03_universe_residuals: 0
task03_duplicate_rows: 0
boundary_delta_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/boundary-disposition-delta-v1.tsv
boundary_delta_sha256: c807d2aa87c0ffb54c3c43bb244be91926206b6a7a40ae8abf9d4d39b9fa667f
boundary_delta_row_count: 137
boundary_addendum_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/boundary-surface-addendum-v1.tsv
boundary_addendum_sha256: c678d415daf01a3f68260a3912ffc0b4596f6b03d4f20a38cf796deb9623b670
boundary_addendum_row_count: 137
effective_matrix_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/effective-disposition-matrix-v1.tsv
effective_matrix_sha256: 34a85a470020329c79b1ffb61810ecd1aca5b795abdf1957d1c12de7fcf3975a
effective_matrix_row_count: 437
state_schema_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/iop-agent-chronos-transfer-state-v1.schema.json
state_schema_sha256: e509b541b26b54401a33a3a1ae0ea8b8581933d64d9f1465bb714969c7eb63d4
state_fixture_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/iop-agent-chronos-transfer-state-v1.json
state_fixture_sha256: 8a71dcc22cee5ed9990cb3a204ca8bc3bc8ffb949618a8a8bb6664a500016148
bundle_receipt_artifact_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/13+09,10_receipt_lock_audit/iop-agent-chronos-transfer-bundle-receipt-v1.json
bundle_receipt_sha256: dab36aac1e3876b7b6a80d0b26a8e89ed59acd06d578db825e0cd79c290625de
state_original_manifest_logical_id_count: 10
state_addendum_logical_id_count: 2
state_logical_id_count: 12
state_logical_ids_sha: 9bce499e286514baeb7ad9bc7c59119a3b5725f69763706dfeba06569d7404b9
state_fixture_record_count: 12
state_fixture_category_count: 8
state_fixture_quarantine_count: 1
real_state_export_location: bundle-member:acceptance-v1/state/state-export-v1.json
real_state_export_sha256: 93e4e19d67a6c29fa0cad8517cf2ca68cd94742bbc1ed5d3eedd3ae079fab74f
real_state_export_record_count: 12
real_state_export_category_count: 8
real_state_export_quarantine_count: 1
bundle_location: withheld-owner-local
bundle_sha256: dd4bea1cd1d39e679f17bcd701d12274eee8298f4065b4f2442b615755439d94
task09_complete_log_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/09+08_remove_agent_ui_close/complete.log
task09_complete_log_sha256: 580fb464b6c0a805a3a9c836ad29328fdc76a9b79cfa1e38660d8f63b26612a6
task10_complete_log_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/10+09_neutral_execution/complete.log
task10_complete_log_sha256: 2f9a2583181ec2fd8d972bfa6a2d1cc33e25e8c8e92f32862f553a07b04e5fbe
import_graph_audit_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/10+09_neutral_execution/node-boundary-audit.log
import_graph_audit_sha256: 462bc1566b37327470d1faa23b1adf7311a5ede12d43364cd0f379bf229db23e
import_graph_residuals: 0
task14_complete_log_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/14+13_remove_migration_surface/complete.log
task14_complete_log_sha256: 88f01218275429a1465553ba9bfdf3c55fdee21b589f3cb486537d4804c39efc
task14_removal_audit_path: agent-task/archive/2026/08/m-iop-agent-chronos-extraction-decoupling/14+13_remove_migration_surface/migration-removal-audit.log
task14_removal_audit_sha256: dad20631157fd35c461e7c72992462a396040aefc19c894b3736eef3a5b48659
task14_removal_status: pass
removed_target_count: 12
pre_delete_digest_count: 12
post_delete_absence_count: 12
final_transfer_remove_residuals: 0
final_retained_path_mismatches: 0
final_renamed_source_residuals: 0
final_unclassified_paths: 0
final_duplicate_paths: 0
provider_node_regression: pass
full_go_regression: pass
readability_audit: pass
forbidden_surface_scan: pass
iop_node_owner: model-provider-device
chronos_connection_surfaces: 0
cli_agent_terminal_workspace_surfaces: 0
chronos_repository_mutations: 0
external_mutations: 0
canonical_promotion: applied
staging_promotion_commit: 7b90b7e5af9035fae2b5349c65eb322096d21b1b
canonical_base_commit: c3a24ec5febab9fc978fd62392efcb6c96e12ec9
canonical_promotion_commit: c8e98d4e10b30114de7bafe426a4045abd6c1205
canonical_promotion_verified_at: 2026-08-02T11:42:32Z
staging_boundary_correction_commit: 7cc9f2d142fac863eff173515f2a81e0a5c9e0f4
canonical_boundary_correction_commit: 81243284cb89206911ec45e99f80701b591c88ae
canonical_boundary_correction_verified_at: 2026-08-02T12:13:14Z
canonical_origin_dev_commit: c3a24ec5febab9fc978fd62392efcb6c96e12ec9
canonical_promotion_push_status: not-pushed
canonical_user_roadmap_restore_status: pass
canonical_user_roadmap_path_count: 3
canonical_recovery_patch_initial_path: /tmp/iop-canonical-promotion.QOCOar/canonical-roadmap.patch
canonical_recovery_patch_initial_sha256: 1f1afe533e910b6311390071e07799bf6cc24ca742853bb45a3eceb3da1d4575
canonical_recovery_stash_initial_commit: a91e4af2bc23c7fab2ef0ea026e2b453e9393848
canonical_recovery_stash_initial_name: iop-canonical-promotion-roadmap-20260802T102956Z
canonical_recovery_patch_prepromotion_path: /tmp/iop-canonical-promotion.QOCOar/canonical-roadmap-prepromotion-20260802T112929Z.patch
canonical_recovery_patch_prepromotion_sha256: 314132fd4943d5a49e47a0fd499c39761bf4ff65f725f129c3a949f7e2927db6
canonical_recovery_stash_prepromotion_commit: ff81ecb5e8b16fc81ecce4273afe24ff06236287
canonical_recovery_stash_prepromotion_name: iop-canonical-promotion-roadmap-20260802T112929Z
integration_conflict_inventory_path: /tmp/iop-canonical-promotion.QOCOar/integration-unmerged.txt
integration_conflict_inventory_sha256: 15e564744e83accd74981a5976432d14d8638815cab0fefc1d1eaabe97a4aca4
integration_conflict_count: 34
integration_unexpected_conflicts_path: /tmp/iop-canonical-promotion.QOCOar/unexpected-conflicts.txt
integration_unexpected_conflicts_sha256: 15e564744e83accd74981a5976432d14d8638815cab0fefc1d1eaabe97a4aca4
integration_qualification_worktree: /config/workspace/iop-canonical-integration
integration_evidence_directory: /tmp/iop-canonical-promotion.QOCOar/evidence
canonical_full_go_regression: pass
canonical_flutter_test_count: 44
canonical_flutter_regression: pass
canonical_web_build: pass
canonical_control_plane_edge_wire: pass
canonical_credential_slot_smoke: pass
canonical_readability_audit: pass
canonical_reconnect_diagnostic: pass
canonical_generation_idempotency: pass
canonical_provider_only_boundary_audit: pass
agent_ui_reconciliation: pass
agent_ui_sync_state_sha256: abce070a46563ebd9b45599882c91814ac1236966c4d3c6a53ce3dbb840f1788
downstream_lock: chronos:chronos-architecture-ownership-boundary
downstream_lock_status: enable
milestone_archive_status: completed
milestone_archived_at: 2026-08-02T12:34:51Z
staging_lock_identity: iop-s1:agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-chronos-extraction-decoupling.md
staging_lock_result: none
canonical_lock_identity: iop:agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-chronos-extraction-decoupling.md
canonical_lock_result: rely-on:chronos:chronos-architecture-ownership-boundary
---
# Handoff: Chronos Extraction Decoupling Ownership Boundary
> D04 전환 경계 정정: Chronos Server와 독립 Chronos Node가 loop, agent, workspace/tool, terminal/PTY, remote host control을 소유한다. Chronos는 필요할 때 IOP external inference API의 일반 client로 작동할 수 있지만, IOP에는 Chronos bridge, control hook, API, proto, config, target registry가 존재하지 않는다.
- 작성일: 2026-07-31 (2026-08-02 D04 정리)
- 현재 타겟: IOP standalone surface 제거 및 D04 경계 수립
- Status: canonical promotion verified; milestone closure and archive completed.
- 기록 위치: IOP 선행 분리 원본은 IOP Roadmap/SDD에, 완료 뒤 제품 개발 원본은 Chronos Roadmap에 둔다.
## Canonical Promotion Receipt
- The reviewed staging commit `7b90b7e5af9035fae2b5349c65eb322096d21b1b` was rebuilt as the single canonical promotion commit `c8e98d4e10b30114de7bafe426a4045abd6c1205` on base `c3a24ec5febab9fc978fd62392efcb6c96e12ec9`.
- A post-promotion boundary correction removed the residual standalone-Agent and terminal-session ownership wording from `packages/go/execution/doc.go`, `packages/go/execution/types.go`, and `apps/node/cmd/node/main.go`, and added the `TestRootCmdUsesProviderOnlyDescription` Node CLI regression. It landed as the staging child commit `7cc9f2d142fac863eff173515f2a81e0a5c9e0f4` on parent `7b90b7e5af9035fae2b5349c65eb322096d21b1b` and the canonical child commit `81243284cb89206911ec45e99f80701b591c88ae` on parent `c8e98d4e10b30114de7bafe426a4045abd6c1205`. Both children carry the identical four-path change set and the same stable patch id `2e89dc0ed8a45542531af2e16efaf397dd3b2602`; neither rewrites the reviewed promotion commit, and canonical-only credential support in `types.go` remains untouched.
- The exact 34-path conflict inventory is stored at `/tmp/iop-canonical-promotion.QOCOar/integration-unmerged.txt` with SHA-256 `15e564744e83accd74981a5976432d14d8638815cab0fefc1d1eaabe97a4aca4`. Every conflict was resolved in favor of the current managed projection, mTLS, credential-recipient, lease, and provider-runtime boundaries while removing standalone Agent, workspace, terminal, and session ownership.
- The canonical `dev` branch was fast-forwarded locally. `origin/dev` remains at the base commit and no push was performed.
- All three pre-existing roadmap edits were restored as unstaged user changes. Both patch generations and both retained stash commits above remain available for recovery.
- The final integration and canonical verification matrix passed full Go tests, 44 Flutter tests, the Flutter web build, Control Plane/Edge wire checks, deterministic credential-slot smoke profiles, readability audit, reconnect diagnostics, generated-source idempotency, and provider-only forbidden-surface scans.
- `console-shell` is reconciled as implemented with no Agent enum, slot, or rail action. The milestone reconciliation record is stored in `agent-ui/.sync-state.json`.
- The Milestone and SDD were archived after the canonical verification gates passed, and the Chronos dependency was changed to `enable`. The canonical branch remains local and was not pushed.
## 사용자 확정 사항
1. `/config/workspace/iop-s0`에서 진행했던 `IOP Agent CLI Runtime` Milestone은 기존 범위대로 완료됐다.
2. Chronos Server 및 독립 Chronos Node가 loop/agent/workspace/tool/terminal/PTY/remote host control 소유권을 전적으로 관리한다.
3. Chronos는 IOP external inference API의 일반 client로 작동할 수 있으나, IOP Node는 Chronos bridge나 control hook을 갖지 않는다.
4. Task 10은 `agent-contract/inner/iop-agent-cli-runtime.md`를 물리 삭제한다.
5. evidence 순서는 다음과 같이 관리하며 이 단계에서 최종 완료를 선포하지 않는다:
- task 10: contract 원문 및 transitional runtime/CLI provider 물리 삭제
- task 13: effective matrix와 pre-deletion receipt 생성
- task 14: 잔여 migration surface 최종 삭제
- task 15: `HANDOFF.md` final composite receipt 작성
## 프로젝트 이름과 상징
프로젝트명은 **Chronos**로 확정한다.
- 영문 문구: `Chronos — Take your time back.`
- 한국어 문구: `일은 맡기고, 시간은 되찾다.`
- 저장소 기본명: `chronos`
- CLI 기본명: `chronos`
- daemon 기본명: `chronosd`
- runtime package/product family: `chronos-runtime`
## 최종 방향
Chronos Server 및 독립 Chronos Node가 agent control과 workspace execution을 독립 수행한다.
IOP는 external inference API와 model/provider/device execution, cancel, status, usage lifecycle만 제공한다.
```text
Chronos Server / Chronos Node -> loop, agent, workspace/tool, terminal/PTY, remote control
Chronos -> optional ordinary client of IOP external inference API
IOP -> no Chronos bridge/API/proto/config/target/registry/control hook
```
책임은 다음과 같이 고정한다.
| 소유자 | 책임 |
|---|---|
| `Chronos` | standalone runtime core, CLI/daemon, local control, workflow adapter, durable state/replay, scoped execution |
| IOP | external inference API, model/provider execution, cancel, status, usage lifecycle |
## Next Steps and Evidence Sequence
1. Task 10은 `agent-contract/inner/iop-agent-cli-runtime.md`를 물리 삭제한다.
2. Task 13: effective matrix와 pre-deletion receipt 기록.
3. Task 14: 남은 migration surface 최종 삭제.
4. Task 15: `HANDOFF.md` final composite receipt 작성.