Task 완료 관측 이관: 원본 분석과 작업별 연결 설계
상태: TTS 내부 연결 구현·JVM 검증 완료. 전체 도메인 이관·장치 연결 완료는 아니다.
이번 변경을 폴더로 이해하기
SVG 확대 · Mermaid 원본 · 전체 프레임워크·폴더 지도
그림은 전체 프레임워크 중 TTS 완료 처리 경로만 확대한다. 전체 지도와 다른 대체 구조가 아니다.
앞부분 skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/를 생략하면:
| 폴더 | 답해야 하는 질문 | 맡기지 않는 책임 |
|---|---|---|
domain/llm/completion/ |
이 재생 세션이 실제로 시작했고 끝났는가? | 다른 도메인 작업의 완료 판정 |
execution/task/completion/ |
이 결과가 등록한 작업·시도·Runtime 세대에 해당하는가? | IDLE 같은 장치 상태를 직접 해석 |
execution/task/ |
작업을 어떤 상태로 전이할 수 있는가? | 스피커 출력·장치 중단 성공을 추측 |
execution/evidence/ |
어떤 사건과 실행 키를 근거로 남기는가? | 근거가 있다는 이유만으로 Task 성공 처리 |
control/lifecycle/ |
담당 서비스가 준비되어 있고 같은 세대인가? | 재생 중·재생 완료 같은 업무 상태 소유 |
예를 들어 이전 작업의 IDLE이 늦게 도착해도 새 작업의 성공으로 바꾸지 않아야 한다. 취소 요청 중 성공 관측이 와도 그것을 “중단 확인”으로 바꾸지 않는다. 시간초과 때 내부 감시를 정리하는 것 역시 실제 스피커가 멈췄다는 뜻은 아니다.
원본의 작업 식별자 없는 콜백은 새 키를 임의로 붙여 연결하지 않는다. 그림의 황색 실행 어댑터는 후속 연결 범위다. Resource·Capability와 Task 기본 등록은 별도 Runtime 구현으로 진행하며 LLM/TTS 장치 결선과는 구분한다.
1. 확인한 현재 상태
TTS 첫 이관: 내부 연결과 회귀 검증 완료
MR7 refactor/blueprint-foundation에 아래 경로를 구현했다. 모두 그림 B~D의 내부 경로이며,
기존 DeviceAgent 상태 콜백을 바로 연결한 것은 아니다.
| 구성요소 | 이번에 구현한 내용 |
|---|---|
domain/llm/completion/TtsTaskCompletionService |
재생 시작 전 IDLE 무시, 시작 후 IDLE 완료, STOP·웨이크워드 중단, 단일 세션과 명시적 정리 |
domain/llm/LlmManagerService |
선택적 완료 서비스 소유와 onStop() 정리 |
execution/task/completion/TaskExecutionKey |
작업·시도·Runtime 세대를 구분하는 불변 키 |
execution/task/completion/TaskCompletionCoordinator |
실행 전 등록, 키·도메인·표적·순서 검증, Task 상태 전이 요청과 근거 기록 |
서버 JVM 33개 클래스 / 193개 검사, SDK JVM 11개 클래스 / 37개 검사가
실패·오류·건너뜀 없이 통과했다. 신규 TTS/Coordinator 25개와 실행 키 1개는 서버 193개에 포함된다.
release Java 컴파일과 폴더·레이어 검사 및 두 검사 도구의 회귀 검사도 통과했다.
전체 Gradle 명령은 BUILD SUCCESSFUL in 11s이며 SDK 테스트는 최신 결과를 재사용했다.
Build Tools 34.0.0 지정 대신 AGP가 35.0.0을 사용하는 기존 경고는 남아 있다.
최종 리뷰에서는 다음 경계를 보완했다.
- 취소 불가인 실행의 실제 중단은
FAILED와 명시적 사유로 기록한다. CANCELLING의 중단 확인은CANCELLED다. - Task 상태 전이 예외가 나면 TTS의 사건·순서 예약을 되돌려 같은 관측을 재시도할 수 있다.
- 외부 경로에서 Task가 종료되거나 종료 이력이 먼저 제거되어도 해당 감시가 다음 실행을 계속 막지 않도록 정리한다.
- 근거 기록·구독자 콜백은 Coordinator·TTS의 상태 잠금 밖에서 수행한다. 같은 사건 ID라도 내용이 다르면 기록 충돌을 알린다.
- 근거 기록 중 겹치는 콜백은
BUSY_RETRY로 반환하며 사건·순서를 소비하지 않는다. 어댑터는 같은 키·eventId·sequence로 반드시 재전달해야 한다. 완료 해석 서비스 자체에는 자동 큐가 없다. 별도 전달 계층은 제공자 연결·TTS 전달에서 구현·검증한다. 실제 생산자·호스트 자동 결선과 구분한다.
남은 연결: 실행 키 없는 onChange(status, ttsType)는 현재 작업에 임의 배정하지 않는다.
Resource·Capability와 Task의 기본 Runtime 구성은 별도 구현 단계로 갱신했다.
실행 요청부터 결과까지 키를 유지하는 어댑터는 여전히 후속 작업이다.
timeout()은 감시만 해제하며 Task의 TIMED_OUT 전이나 스피커 정지를 합성하지 않는다.
서비스 세대가 바뀌었는데 onStop()을 거치지 않는 복구 경로는 명시적 cleanup이 필요하다.
근거 전달의 도메인 간 전역 순서는 보증하지 않는다. APK·AOSP 제품 이미지·실기기 완료 검증은 하지 않았다.
소스 보고서: docs/refactor/skix-tts-completion-port-2026-09-08.md.
후속 실행 검증: 원본 JVM 250개 통과
2026-09-08에 아래 MR6 커밋의 apps/DeviceAgent를 git archive로 임시 디렉터리에 추출했다.
사용 중인 MR6 브랜치를 전환하지 않았고, 원본 생산·테스트 소스를 변경하지 않았다.
Watcher·TTS 8개를 먼저 실행한 후 Task 패키지와 보안 완료 보고까지 확대해
28개 테스트 클래스 / 250개 검사, 실패·오류·건너뜀 각각 0을 확인했다.
8개는 250개에 포함되므로 합산하지 않는다.
| 이관할 책임 | 원본 테스트 | 수 | MR7 소유 폴더 |
|---|---|---|---|
| 완료 감시·결과 우선순위 | TaskCompletionWatcherTest | 5 | execution/task/와 도메인 완료 해석 분리 |
| 시작 후 TTS 완료·취소 | LlmTaskCompletionBridgeTest | 3 | domain/llm/ |
| 주행 전 청정 정지·이미 스테이션인 경우 | MovementTaskExecutorTest | 7 | domain/amr/와 선행 조건 조정 |
| 이미 운전·정지한 청정 상태 처리 | CleaningAmpTaskExecutorTest | 8 | domain/cleaning/ |
| 앱 세션·보안 완료 보고 | AppSessionCompletionBridgeTest / WssTaskCompletionReporterTest | 8 | domain/ui/, domain/security/ |
| 도메인 자원 충돌 조건 | TaskDomainResourcePolicyTest | 9 | control/resource/, control/policy/와 도메인 요구 조건 |
| 작업 관리·복합 흐름 | TaskManagerTest / TaskManagerIntegrationTest | 94 | execution/task/, execution/workflow/ |
| 관측·계획 입력·모니터링 등 나머지 | 19개 클래스 | 116 | 후속 책임 분류 |
원본 프로젝트에서 실행한 명령:
ANDROID_HOME=/home/silogood/Android/Sdk ANDROID_SDK_ROOT=/home/silogood/Android/Sdk \
./gradlew --no-daemon :app:testDebugUnitTest \
--tests 'com.sk.airbot.deviceagent.task.*' \
--tests com.sk.airbot.deviceagent.wss.WssTaskCompletionReporterTest
원본 wrapper 8.9 / AGP 7.3.0 / OpenJDK 17.0.20을 사용했다.
확대 실행은 BUILD SUCCESSFUL in 27s이며 Gradle 작업 1개 실행·20개 최신 상태였다.
테스트 수는 JUnit XML의 suite 속성을 합산했다. Manifest·Gradle deprecation 경고는 남아 있다.
검증 한계: 원본의 테스트용 Bundle·TextUtils와 가짜 장치 실행기를 사용한 JVM 결과다.
실제 모터·팬·TTS 출력, Android Binder, MR7 이관 동등성은 검증하지 않았다.
이 원본 테스트 실행 시점에는 신규 Coordinator가 설계 검토안이었다. 이후 TTS 내부 연결을 구현·검증했으며,
Resource·Capability 구현과 Task의 기본 Runtime 연결 결과는 후속 구현 문서를 참조한다.
재현 경로·28개 클래스별 결과는 MR7 저장소의
docs/strategy/short-term/2026-09-08-mr6-task-migration-baseline.md에 기록했다.
설계 분석 시점의 소스 상태
MR7 기준 커밋은 2d462ad, 브랜치는 refactor/blueprint-foundation이다.
execution/evidence/EvidenceManagerService는 관측 보관을 맡지만 Task 완료를 판정하지 않는다.
execution/task/TaskManagerService.transition()은 상태 전이와 사유만 검사하며 관측 식별자를 요구하지 않는다.
이 분석 시점의 domain/amr/AmrManagerService, domain/llm/LlmManagerService는 생명주기 소유권 클래스였다.
이름이 존재하는 것을 실제 완료 판정·장치 연결 구현으로 해석하면 안 된다.
MR6 원본은 a1-packages-mr6의 task_manager 커밋
d1d8dfbc4c807a32990f4350cf09c49f159f5848이다. 현재 MR6 체크아웃은 다른 브랜치이므로
git show task_manager:<path>로 읽었다. 이 문서의 MR6 경로 앞부분은
apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/다.
2. 완료 표적 24개의 도메인 소유권
아래 값은 task/TaskCompletionStateStore.java:15-38의 원본 문자열이다.
원본 wire 값을 유지하되 공통 저장소에 모든 도메인의 상수를 몰아넣지 않는다.
소유권 열은 이관 설계이며 신규 파일이 이미 있다는 뜻이 아니다.
| 원본 표적 | 현재 생산자 / 연결 근거 | 이관 소유권 |
|---|---|---|
movement.arrived, movement.stationCharging, movement.rotationCompleted |
moving/RobotMovementController.java:643, moving/MovingLocationManager.java:596, moving/MovingController.java:56 |
domain/amr/ |
cleaning.started, cleaning.stopped, cleaning.stepComplete, cleaning.reportDone |
cleaning/BaseCleaningManager.java:216, cleaning/BasicCleaningManager.java:227, cleaning/MovingCleaningManager.java:737 |
domain/cleaning/ |
interaction.started, interaction.arrived, interaction.actionStarted, interaction.actionEnded, interaction.returning, interaction.completed |
interaction/InterScheduleManager.java:502 |
복합 순서는 execution/workflow/, 개별 실행 판정은 담당 domain. 세부 이관은 이번 첫 TTS 연결 범위 밖 |
security.started, security.paused, security.resumed, security.stopped, security.patrolCompleted |
wss/WssTaskCompletionReporter.java, wss/WssManager.java:1149 |
domain/security/ |
mapping.dataReceived |
main/MainService.java:121 |
domain/amr/ |
app.sessionStarted, app.sessionEnded |
task/AppSessionCompletionBridge.java:31 |
domain/ui/의 앱 세션 관측 경계 |
tts.playbackStarted, tts.playbackEnded |
task/LlmTaskCompletionBridge.java:60 |
domain/llm/ |
ui.screenApplied |
main/api/MainApi.java:387 |
domain/ui/ |
areaId는 실제로 방 번호만 담지 않는다. TTS에는 completionKey, 앱에는 packageName,
상호작용에는 별도 key가 들어간다. 새 공통 계약에서 이를 무조건 공간 ID로 정의하지 않는다.
3. 도메인별 보존해야 할 완료 조건
주행
task/MovementTaskExecutor.java는 주행 명령 전에 청정이 동작 중이면 청정 정지를 요청하고
정지 관측을 기다린다. 주행·청정 간 전이 정책이 이미 Task 실행기에 들어 있는 사례다.
이 정책은 AMR이 Cleaning 내부 상태를 직접 읽는 방식으로 복제하지 않고 제어 계층과 도메인 협력으로 분리해야 한다.
waitForMovementArrived()는 명시적 도착 관측뿐 아니라 현재 위치·주행 여부를 확인한다.
대상 위치이며 주행 중이 아니고, 이미 대상에 있었거나 이동을 실제 관측한 경우
2초간 위치가 유지되면 도착으로 인정한다. 그 뒤 waitForMovementIdle()은 최대 30초간
컨트롤러 IDLE이면서 returning·docking이 아닌 상태를 별도로 기다린다.
충전 복귀는 이미 스테이션에 있으면 즉시 결과를 내는 별도 분기다.
따라서 범용 Evidence 이벤트 하나로 이 조건들을 대체하면 원본 사양이 사라진다.
청정
task/CleaningAmpTaskExecutor.java는 명령 전 상태를 보관한 뒤 이미 해당 구역에서 시작한 상태,
이미 정지한 상태를 각각 인정한다. 기본 운전은 cleaning.stopped, 다른 운전은
cleaning.reportDone을 기본 표적으로 선택하며 요청 가능한 표적도 제한한다.
시작 후 정지가 표적인 경우 dwell 시간을 제한한 뒤 정지 명령을 보낸다.
청정 시작·단계 완료·전체 보고 완료는 서로 다른 사건이다.
TTS
task/LlmTtsTaskExecutor.java는 tts.playbackEnded 완료를 요청한 경우에만 키를 요구하고 감시한다.
키 후보는 completionKey → 외부 step ID → requestId → traceId 순서다.
task/LlmTaskCompletionBridge.java는 활성 재생 한 건을 보관하고, 처리·재생 상태를 본 뒤에만
IDLE을 완료로 인정한다. 시작 전 IDLE은 무시한다. 웨이크워드 취소와 STOP은 중단이다.
단순 메서드 반환이나 시작 전 IDLE을 완료로 바꾸면 안 된다.
앱과 시스템 계열
task/AppSessionCompletionBridge.java는 running을 먼저 본 패키지만 종료 대상으로 인정한다.
취소 별칭과 실패 별칭을 구분하지만, 그 외 running 이후 상태는 종료 성공으로 처리한다.
이 fallback을 새 엄격한 계약으로 확대 해석하지 않고 호환 어댑터 동작으로 명시한다.
task/MainApiTaskExecutor.java에는 보안 결과 코드 검사, 지도 수신 감시, 예약 저장 후 검사가 함께 있다.
이를 하나의 공통 ManagerService로 복제하지 않고 각각 해당 도메인·예약 경계로 분리한다.
4. 공통 저장소·감시기의 실제 제약
| 소스에서 확인한 동작 | 이관 시 의미 |
|---|---|
TaskCompletionStateStore.put()은 target|area와 target|빈값을 모두 갱신 |
빈 키 조회는 모든 구역의 최신 사건을 볼 수 있음. 작업별 식별이 아님 |
| 성공·중단을 별도 맵으로 보관 | 양쪽이 동시에 남을 수 있음 |
TaskCompletionWatcher.waitFor()는 성공 검사 후 중단 검사 |
둘 다 있으면 성공 우선. TaskCompletionWatcherTest.waitFor_whenCompletionAndAbortObserved_shouldPreferCompletion이 명시적으로 보장 |
표적·구역 문자열을 |로 결합 |
임의 문자열의 구분자 충돌 가능. 새 내부 키는 구조화된 값으로 분리 |
observedAt >= since와 사건 이후 경과시간으로 안정성 판정 |
지속 상태를 관측한 것이 아님. 동일 표적 재발행은 시각을 덮어씀 |
| 벽시계 deadline, 500ms sleep, 최소 timeout 1초, 기본 30초, 기본 stable 500ms | 새 서비스 스레드를 블로킹하지 않는 스케줄링 경계와 시간 주입이 필요 |
| TTS timeout 경로는 bridge 활성 항목을 해제하지 않음 | 상태 콜백이 오지 않으면 다음 begin이 resource_busy가 될 수 있는 정적 위험. 실기기 재현 결과는 아님 |
TaskModuleExecutor.writeWiredResult()는 실행 메서드 반환 후 wired=true 기록 |
wired는 장치 완료 증거가 아님 |
성공 우선 규칙을 전역 중단 우선으로 변경하지 않는다. 주행의 정상 도착 후 IDLE도 중단 관측을 기록할 수 있으므로 도메인이 정상 후속 상태인지 실제 중단인지 먼저 해석해야 한다.
5. 비교와 권장안
- 원본 Store/Watcher를 그대로 복사: 호환성은 높지만 전역 키·Bundle·폴링·도메인 조건 혼재가 남는다. 목표 구조와 맞지 않는다.
- 권장: 도메인별 완료 조건 + 작업 시도별 공통 연결: 원본 사양은 도메인으로 옮기고 Task에는 작업·시도·취소·시간초과 관리만 둔다. 기존 wire 해석은 compatibility에 둔다.
- 모든 Evidence 이벤트로 Task를 자동 성공 처리: 구현은 짧지만 시작/단계/전체 완료와 재시도 관측을 혼동한다. 채택하지 않는다.
6. 신규 계약 제안과 폴더 배치
아래는 승인된 전체 이관 계약이다. TTS 내부 경로부터 구현하며, AMR·Cleaning·UI·보안·호환 변환의 모든 행이 이미 구현되었다는 뜻은 아니다.
Java 서버 공통 패키지 루트는 skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/다.
| 신규 파일 / 위치 | 단일 책임 |
|---|---|
execution/task/completion/TaskExecutionKey.java |
taskId·attemptId·runtimeGeneration을 별도 필드로 보관. 문자열 결합이나 areaId를 작업 ID로 쓰지 않음 |
execution/task/completion/TaskCompletionObservation.java |
실행 키, eventId, 도메인, 표적, 관측 순번, 정규화된 도메인 결과 보관 |
execution/task/completion/TaskCompletionCoordinator.java |
현재 실행 키와 사건을 연결하고 중복·이전 시도·종료 후 사건을 거부. TaskManagerService만 상태 writer로 사용 |
domain/amr/completion/ |
도착·위치 안정화·IDLE·스테이션 조건과 AMR 완료 상수 |
domain/cleaning/completion/ |
청정 시작·정지·단계·보고 조건과 상수 |
domain/llm/completion/ |
시작 관측 이후 TTS 완료·취소 조건과 활성 세션 정리 |
domain/ui/completion/, domain/security/completion/ |
앱/화면 및 보안 상태를 각 도메인의 결과로 변환 |
base/compatibility/java/ |
기존 completionTarget·completionKey·시간 Bundle 필드 해석. 원본 문자열 상수·매핑 유지 |
base/services/tests/src/.../ |
각 생산 패키지를 따라 원본 동작과 신규 상관관계 테스트 배치 |
공개 SDK에 도메인 이벤트를 노출할 때만 base/core/java/의 해당 도메인 계약으로 승격한다.
내부 완료 표적 전부를 공개 API로 고정하지 않는다.
사건 접수 규칙
- 실행 시작 전에 작업 시도 키와 도메인 연결을 등록한다. 등록 전 관측·이전 runtimeGeneration·다른 attemptId는 현재 Task를 종료할 수 없다.
- 도메인에서 의미가 확정된 완료·실패·중단만 Coordinator에 전달한다. 처리 시작, raw IDLE, 임의 Evidence 문자열은 terminal 결과가 아니다.
- key가 없는 기존 사건을 최신 Task에 추측 배정하지 않는다. 호환 어댑터가 독점 실행과 현재 요청의 상관관계를 증명하지 못하면
uncorrelated_observation으로 보고하고 상태는 유지한다. - Task가 CANCELLING이면 success를 CANCELLED로 바꾸지 않는다. 도메인의 실제 중단 확인을 기다린다. timeout은 시간초과이지 장치가 멈췄다는 증거가 아니다.
- 중복 eventId와 종료 후 사건은 재전이를 만들지 않는다. 보관 한도·종료 시 정리는 Task 기록 보관 정책과 함께 테스트한다.
- READY가 아닌 서비스는 신규 접수를 거부한다. 중지·timeout에서 구독·시도 연결을 정리하되 물리 정지를 합성하지 않는다.
- Evidence는 동일 eventId와 실행 키를 사용해 근거를 보관한다. 보관 실패와 Task 전이를 하나의 트랜잭션으로 보장한다고 주장하지 않는다. 실패를 숨기지 않고 근거 보관 결과를 별도로 반환한다.
7. 구현 순서와 통과 조건
- 원본 감시기 테스트 5개와 TTS bridge 테스트 3개를 특성 테스트로 보존한다. 원본 success 우선, 시작 전 IDLE 무시, 사용자 취소를 포함한다.
- 첫 세로 연결은 TTS로 한정한다. 단일 활성 재생이라는 원본 제약을 보존하면서 완료 키·시도·시작 관측·취소·timeout 정리를
domain/llm에서 구현한다. 이는 다른 도메인의 제외가 아니라 첫 검증 경로다. - Coordinator 테스트: 일치/불일치 task·attempt·generation, 등록 전 사건, 중복, 종료 후 사건, CANCELLING+success, 실제 취소 확인, 준비 상태 장애, 근거 보관 실패.
- AMR·Cleaning을 각각 이식한다. 도착 후 IDLE, 2초 위치 안정화, 청정 정지 선행, 이미 실행/정지 상태, 시작·단계·보고 분리를 검증한다.
- UI·보안·상호작용을 실제 생산자에 연결한다. 상호작용은 Workflow와 개별 도메인의 책임을 추가 분석한 뒤 나눈다.
- 기본 구성에 Task를 넣기 전에 실제 Resource·Capability 서비스 의존성을 구현한다. 테스트용 빈 구현으로 제품 기본 구성을 채우지 않는다.
- 전체 JVM·release 컴파일·레이어 검사 후 Android 전달·기기 검증으로 확장한다. 기기 검증 전에는 물리 완료 통합 완료로 보고하지 않는다.
8. 검증 수준
최초 설계 단계는 위 파일과 테스트 본문을 읽은 정적 원본 분석이었다. 이후 원본 JVM 실행 결과는 1절에 추가했다. 최초 분석에서는 MR6 테스트를 실행하거나 실기기에서 경쟁·timeout을 재현한 것은 아니다. 이전 Evidence 단계의 JVM 167/37 통과를 이 신규 Coordinator 설계의 검증으로 재사용하지 않는다.