← Docs hub

AI Agent와 프레임워크 — 누가 실행하고 누가 작업을 관리하나

TTS의 실제 합성·재생은 AI Agent 쪽 기능이다. 프레임워크로 TTS 엔진을 옮기는 작업이 아니다. 프레임워크는 그 실행을 어떤 작업에 연결할지, 결과를 받아도 되는지, 연결이 끊겼을 때 무엇을 정리할지를 관리한다.

이 문서는 전체 모듈 지도를 대신하지 않는다. 전체 지도에서 헷갈리기 쉬운 LLM 도메인 ↔ AI Agent ↔ TaskManager 부분을 설명한다. TTS는 이 관계를 설명하는 사례 하나이며 프레임워크 전체를 TTS 중심으로 만드는 것이 아니다.

최신 진행: 발화 입력에서 실행 제공자까지

이번 범위는 내부 TTS 실행 연결이며, 실제 AI Agent 연결은 미완료다. 기존에는 공통 Task 접수와 TTS 완료 감지가 각각 있었다. 이번에는 그 사이에 LlmTtsInput과 TtsTaskDispatcher를 두어, 문장과 실행 식별자가 테스트용 실행 제공자까지 도달하고 그 실행의 결과만 원래 Task에 반영되는 경로를 연결한다. 코드 95f04cd에서 내부 Task1의 선택적 통합 완료를 확인했다. LLM 추가 확장은 보류하고 AMR·Cleaning 등 다른 도메인 이관을 우선한다. 아래 상세도는 내부 연결 범위를 설명한다. 내부 명세·품질 검토 통과와 실제 제품 통합은 별개다.

TTS 입력부터 작업 접수·제공자 호출·결과와 취소까지의 폴더별 상세 구조

TTS 실행 상세도 크게 열기 · Mermaid 원본

위 그림은 아래의 전체 역할 구조도를 보완한다. 번호는 입력 접수 → 작업 선택 → 감지 등록 → 전송 → 결과 처리 순서이며, 실선은 서버 내부 책임, 주황 점선은 아직 연결하지 않은 실제 AI Agent 경계다.

폴더·구성요소 (skix-framework/base/ 기준) 맡는 역할 완료로 오인하면 안 되는 것
services/core/…/domain/llm/execution/LlmTtsInput 실행할 문장을 변경 없이 보관하는 불변 입력 음성 합성이나 Bundle 별칭 해석
같은 domain/llm/execution/의 TtsTaskDispatcher 입력 접수, 한 번의 실행 선택, 제공자 호출과 취소 전달 정책 승인이나 자원 임대를 대신하지 않는다
services/core/…/execution/task/TaskManagerService 작업·입력의 원자적 접수, 대기열, 작업 상태의 단일 변경 제공자 호출이 반환됐다는 이유로 성공 판정
services/core/…/domain/llm/completion/TtsManagedHost 연결·발화 등록, 실행별 관측 전달과 종료 정리 실제 Android Service 프로세스 기동
같은 폴더의 TtsTaskCompletionService 해당 발화의 시작·종료·중단 통지를 Task 결과로 해석 전역 IDLE을 현재 발화에 임의로 연결
기존 AI Agent 실제 문장 합성·스피커 재생 이번 테스트용 제공자로 검증된 구현이 아님

요청과 결과를 읽는 순서

  1. 입력과 작업을 함께 접수: 문장은 LLM 도메인에, 대기·진행 상태는 공통 Task에 맡긴다.
  2. 등록 후 전송: 선택한 작업에 서버가 실행·발화 식별자를 붙이고 완료 감지를 먼저 등록한다. 즉시 돌아오는 결과를 놓치지 않기 위해서다.
  3. 실행 승인과 제공자 호출: 공통 TaskManager의 beginExecution(claim)으로 실행 승인을 받은 뒤 같은 문장과 식별자를 실행 제공자에 넘긴다. 호출 중에는 공통 Task 잠금을 잡지 않는다.
  4. 결과 대기: 호출 반환은 성공이 아니다. 같은 연결·실행에서 온 시작과 종료 관측을 검증해야 해당 작업이 끝난다.
  5. 취소 전달과 중단 확인: 취소 의도와 실제 중단은 별개다. 취소 명령을 보냈더라도 중단 확인 전에는 CANCELLING으로 남는다.

전송 여부 미확인: 제공자 호출이 예외를 냈다고 “아예 실행되지 않았다”고 추측할 수 없다. 이 경우 자동 재전송·자동 성공·자원 해제를 하지 않는다. 연결 종료나 서비스 종료도 스피커 정지의 증거가 아니다.

이 진입점은 신뢰된 서버 내부 연결이다. 앱에 공개하는 SDK/Binder API가 아니며, source 문자열을 호출자 신원으로 인정하지 않는다. 정책 검사·자원 확보·호출자 권한 검증을 갖춘 운영 경로를 조립한 뒤에만 제품 요청으로 연결할 수 있다. 현재 timeoutMs는 기존 Task 메타데이터의 기록값이며 제한 시간 타이머를 구현했다는 뜻이 아니다. 이 내부 단계는 재시도나 동기 대기를 지원한다고 표시하지 않으며, 실제 공개 계약은 별도 검증 대상이다.

MR6에서 유지하는 것과 아직 옮기지 않은 것

MR6 LlmTtsTaskExecutor의 문장 원문 보존과 완료 감지 등록 후 실행을 기준으로 삼는다. 기존 Bundle의 tts → text → ttsText → message 첫 non-null 선택은 호환 변환 책임이다. 새 내부 입력은 null을 거부하지만 빈 문자열·공백을 임의로 바꾸지 않는다. 기존 legacy 호출은 변경하지 않는다. MR6의 120초 완료 대기, 실제 Agent 전송, Bundle 변환 전체를 이번 연결로 이식 완료했다고 표시하지 않는다.

아래 본문의 343개·360개 등의 테스트 수치는 이전 커밋의 검증 이력이다. 이번 내부 실행 연결의 최신 검증 결과는 아래 코드 기준 체크포인트에서 구분한다.

2026-09-14 검증 체크포인트: 작업 상태와 실제 실행을 분리

코드 95f04cd 기준 서버 JVM 723개·SDK JVM 62개, 실패·오류·건너뜀 0이다. release Java 컴파일과 폴더·레이어·API 경계 검사 3종도 통과했다. 이는 실제 AI Agent·스피커· Android 부팅 검증이 아니다. 내부 Task1은 전체 명세 재검토·전체 품질 검토 통과 후 선택적으로 커밋했다.

상황 공통 TaskManager 책임 LLM 도메인 책임
실행 승인 이전의 취소 beginExecution(claim)에서 이미 취소된 claim을 거부 제공자를 호출하지 않고 실행 전 실패와 세션 정리
실행 승인 이후의 취소 논리적인 실행 시작 이후의 취소 의도 기록 제공자에 취소를 전달하되 실제 STOP 관찰 전 중단 완료로 간주하지 않음
이전 작업의 외부 종료 기존 Task의 종료 상태와 다음 대기 작업 보존 정확한 이전 세션을 닫고, 이전 관찰 처리 중에는 다음 Task를 QUEUED로 유지
실행 전 실패 기록의 장애 실패 상태 기록 자체가 실패하면 RUNNING이 남을 수 있다 제공자를 호출하지 않은 예약과 상관 데이터 정리. Task 복구 완료로 과장하지 않음

작업 선택에 따른 RUNNING, 논리적인 실행 시작, 실제 재생 시작은 서로 다른 사실이다. 공통 실행 권한은 services/core/…/execution/task/에 두고, 발화 전달·관찰 처리는 services/core/…/domain/llm/{execution,completion}/에 유지한다. 도메인별 TaskManager를 새로 만드는 변경이 아니다. 준비 상태와 재생 슬롯 조회도 자원 임대나 물리 정지 보장이 아니다.

서비스 종료와 겹친 접수의 순서, Task 기록 장애 복구, 운영 경로의 단일 실행 등록자 조립, 실제 Agent·권한·자원·watchdog 연결은 후속 검증 대상으로 남긴다. 코드 근거는 MR7의 docs/refactor/skix-tts-task-dispatch-2026-09-09.md이며, 코드 통합과 문서 공개 여부, 실제 기기 검증은 각각 구분한다.

1. 구조를 읽는 순서

먼저 구분할 세 가지: 기능, 관리 코드, 실행 프로세스

TTS라는 기능은 AI Agent와 연계되어 동작한다. 이를 프레임워크에서 다룬다는 말은 AI Agent 안의 합성 엔진을 다시 구현한다는 뜻이 아니라, “이 발화가 어떤 요청이며 지금 끝났는가”를 주행·청정 운전 등 다른 기능과 같은 작업 관리 체계에서 다루겠다는 뜻이다.

구분 TTS 사례 폴더와 현재 상태
실제 기능 문장을 음성으로 만들고 재생 별도 AI Agent 저장소의 기존 음성 실행 경로
기능별 관리 AI Agent 연결과 발화 결과 전달·종료 정리 base/services/core/…/server/domain/llm/ 내부 구현. 제품 연결은 후속
공통 작업 관리 요청별 대기·진행·취소·종료 상태 관리 base/services/core/…/server/execution/task/ 내부 구현. 외부 Task Binder는 후속
Android 실행 프로세스 위 관리 객체를 만들고 실제로 시작·종료 base/services/runtime/의 Android Service 호스트는 아직 미구현

여기서 base/는 skix-framework/base/다. ManagerService마다 별도 APK를 만드는 구조가 아니다. 도메인별 책임과 수명주기를 분리하면서, 목표 구조에서는 격리된 Framework Runtime 프로세스가 관리 객체들을 조립한다. TtsManagedHost의 Host는 내부 연결 관리 객체라는 뜻이며, Android 프로세스를 시작하는 호스트와 다르다.

지금 코드가 있는 것과 제품에서 도는 것을 나누어 읽기

2026-09-09 소스 재확인 기준, apps/SkixFrameworkService/app/src/main/AndroidManifest.xml에는 application만 있고 Service 선언은 없다. createBaseRuntime()의 기본 7개 서비스는 Java 객체 조립이지, 부팅된 기기에서 이미 실행 중인 7개 Android Service라는 뜻이 아니다. Root Binder 구현은 있지만 이를 실제 호스트에서 생성·공개하는 연결, Task 전용 Binder, SystemServer 시작 구현과 제품 설치 모듈은 후속이다.

따라서 아래 상세 그림의 내부 실선은 구현된 객체 사이의 책임 관계로 읽는다. AI Agent부터 새 Framework Runtime까지 실제 제품 통신이 완료됐다는 표시가 아니다. 전체 시작 구조와 남은 연결은 전체 구조의 실행 단계 설명을 함께 본다.

AI Agent 실행 영역과 프레임워크 관리 영역, 기존 상태 통지와 목표 실행별 결과 경계

구조도 크게 열기 · Mermaid 원본

먼저 왼쪽의 AI Agent와 오른쪽의 프레임워크를 구분한다. 다음으로 오른쪽 위의 도메인 연결 관리, 가운데의 결과 전달, 아래의 공통 작업·자원 관리를 읽는다. 점선은 아직 이관하지 않은 연결 또는 설계 단계다. 실선도 종단 제품 검증 완료를 뜻하지 않는다. 상자 개수는 APK나 프로세스 개수가 아니라 책임의 구분이다.

2. 비슷한 이름을 구분하기

“TTS는 AI Agent와 연계해서 도는 것 아닌가?”

맞다. 실제 음성 기능은 AI Agent에서 돌고, 프레임워크는 그 기능을 다른 로봇 작업과 함께 관리할 수 있도록 연결한다. LlmManagerService는 새 LLM 모델이나 별도 TTS 서버를 뜻하지 않는다.

상황 AI Agent가 할 일 프레임워크가 할 일
문장을 읽어 달라는 요청 기존 합성·재생 경로로 소리를 냄 어느 작업·발화의 요청인지 구분하고 연동 상태를 관리
재생 시작·종료 실제 실행에서 발생한 결과를 통지 원래 실행의 결과인지 확인한 뒤 해당 Task에 반영
연결이 끊김 재접속·실제 재생 상태 확인에 협력 이전 연결의 늦은 결과를 차단하고 대기 데이터를 정리
음성 중 다른 기능 요청 요청자로서 주행·청소 등 기능을 요청할 수 있음 해당 도메인·정책·자원 경로로 처리하며 LLM에 다른 기능 책임을 몰지 않음

이 표는 목표 역할 분담이다. 아래의 내부 관리 구현은 있지만 실제 AI Agent에서 실행 식별자를 보존해 결과를 돌려주는 연결은 아직 남아 있다. 현재 전역 음성 상태를 받는 것과, 새 Task에 연결된 발화 결과를 받는 것은 같은 구현 단계가 아니다.

이 페이지는 두 번에 나누어 읽어도 된다

처음에는 이 역할 표와 3절의 발화 흐름, 4절의 구현 상태, 6절의 폴더까지만 읽으면 된다. ProviderConnectionToken·큐·close() 같은 내부 계약은 구현을 수정하거나 장애를 분석할 때 보는 상세 설명이다. 상세 내용을 삭제하지 않고 읽는 깊이를 나눈다.

이름 실제 의미 하지 않는 일
AI Agent 음성 인식·추론·TTS 등 실제 AI/음성 기능을 수행하는 기존 실행 구현 프레임워크 전체의 작업·자원 소유권을 대신하지 않음
SDK Manager 앱이 프레임워크에 요청할 때 사용하는 공개 호출 창구 서비스 프로세스나 TTS 엔진 자체가 아님
LlmManagerService 프레임워크 내부 LLM 도메인의 수명주기와 연동 책임자 이름에 LLM이 있어도 언어 모델 서버 자체가 아님
TtsManagedHost 내부 구현인 AI Agent TTS 실행 연동 관리 객체. 연결·전달 큐·종료 정리를 묶음 Android Service·새 APK·TTS 합성 엔진이 아님
TtsObservationDelivery 실행 식별자가 있는 결과를 순서대로 완료 처리에 전달하는 기존 큐 현재 실행 키를 추측해서 붙이지 않음
TtsTaskCompletionService 발화 시작·종료·중단 관측을 기존 Task 완료 계약으로 해석 스피커 출력이나 물리 정지를 직접 확인하지 않음
TaskManagerService 작업이 실행 중인지, 성공·취소·실패했는지 관리 TTS 합성과 재생 구현을 포함하지 않음

LlmManagerService와 새 관리 객체의 구현 대상 언어는 기존 서버 계층과 같은 Java다. AI Agent 비교 소스의 ForegroundService.kt는 Kotlin이며, 이번 문서 정리에서 그 앱은 변경하지 않는다. TtsManagedHost라는 코드명보다 “AI Agent 연동 아래의 TTS 실행 어댑터”라는 역할로 먼저 이해하면 된다.

코드 용어를 일상적인 질문으로 바꾸면

용어 여기서 답하는 질문 예시
연결 토큰 (ProviderConnectionToken) 지금 연결된 실행 제공자의 결과가 맞는가? 재연결 전의 늦은 통지는 새 연결의 결과로 받지 않는다.
작업 실행 키 (TaskExecutionKey) 어떤 작업의 몇 번째 실행에 대한 결과인가? 같은 작업을 다시 시도해도 이전 시도의 완료와 구분한다.
발화 식별자 (completionKey) 그 실행 안에서 어느 발화인가? 다른 발화의 IDLE로 현재 발화를 완료하지 않는다.
관측 (observation) 실행 쪽에서 무엇이 일어났다고 알렸는가? 재생 시작·종료 상태를 이벤트와 순번을 붙여 전달한다.
전달 큐 (TtsObservationDelivery) 먼저 도착한 결과부터 안전하게 처리할 수 있는가? 완료 처리가 바쁘면 같은 항목을 유지하고 다음 전달을 기다린다.

여기서 관측은 결과 통지이지 장치에 내리는 명령이 아니다. 또한 이벤트를 받았다는 사실만으로 스피커에서 실제 소리가 났다고 보증하지 않는다.

3. 한 번의 발화를 따라가기 — 목표 연결 흐름

아래 순서는 목표 흐름이다. 실제 생산자에서 실행 키를 끝까지 전달하는 이관은 아직 남아 있다.

  1. 요청 접수: 앱이나 상위 서비스가 발화를 요청한다. 필요한 Policy 검사와 Resource 확보는 공통 실행 경로의 책임이다.
  2. 실행 식별: Task의 작업·시도 식별자와 발화 식별자를 연결한다. 메시지를 보냈다는 사실을 재생 수락으로 간주하지 않는다.
  3. AI Agent 실행: AI Agent가 실제 합성·재생을 수행한다. 연결 어댑터는 이 실행을 대체하지 않는다.
  4. 실행별 결과 반환: 생산자가 원래 실행 식별자, 이벤트 식별자, 순번, 상태를 돌려준다. 식별자 보존을 위한 실제 전송 계약은 후속 이관 대상이다.
  5. 프레임워크 판정: 연결과 실행이 여전히 유효한지 확인하고 전달 큐를 거쳐 해당 Task의 결과를 처리한다.
  6. 별도 자원 확인: 작업 결과와 자원 재사용 가능 여부는 구분한다. 연결 끊김만으로 재생이 멈췄다거나 자원이 FREE라고 처리하지 않는다.

AI Agent가 음성 명령을 해석해 다른 로봇 작업을 요청하는 경우에는 요청자 역할도 한다. 그 역할과 여기서 설명하는 TTS 실행 제공자 역할을 구분해야 한다. 하나의 앱이 두 역할을 가진다고 프레임워크의 Task 상태와 제품 정책까지 그 앱에 모으지는 않는다.

4. 지금 구현된 것과 아직 남은 것

내부 호스트 구현 기준은 MR7 refactor/blueprint-foundation의 34c08bd다. 초기 설계 문서 f161ec3 이후 내부 관리 객체를 구현했다. 실제 생산자·Android 연결은 별도 후속이다. 이후 상태 분류(c960924), 실제 콜백 특성 테스트(0bd43fe), 송신 계약 상수화(b2b45fa)까지 진행했다. 아래의 호스트 검증 수치는 해당 커밋 당시 기록이며 최신 후속 결과는 6절에서 구분한다.

항목 현재 상태 해석
LLM 도메인 서비스 내부 구현 선택 구성한 호스트를 onStop()에서 닫고 완료 상관 상태도 정리함
연결 토큰과 준비 상태 순서 검사 내부 구현 존재 실제 Binder 연결 관리 전체가 완성됐다는 뜻은 아님
TTS 관측 큐와 Task 완료 처리 내부 구현 존재 실행 식별자가 있는 입력에 대한 처리임
TtsManagedHost의 통합 소유권 내부 구현 연결을 소유하고 기존 큐에 위임함. Task·발화 상태를 호스트에 중복 저장하지 않음
AI Agent의 실행별 결과 전달 이관 필요 기존 전역 상태 통지에는 실행 키가 없음
Android 부팅·실제 스피커 종단 검증 미완료 이번 Wiki 배포로 검증되는 항목이 아님

기본 런타임의 실제 조립은 여전히 Runtime·State·Evidence·Policy·Resource·Capability·Task 7개다. LLM을 비롯한 도메인 클래스가 있다고 기본 구성에 자동 등록된 것으로 읽으면 안 된다.

이 커밋에서 서버 JVM 39개 클래스·290개 테스트, SDK JVM 11개 클래스·37개 테스트를 전체 강제 재실행해 통과했다. 실패·오류·건너뜀은 0이며 release Java 컴파일도 통과했다. 새 호스트 테스트 17개는 위 서버 수치에 포함된다. 실제 앱 설치·AI Agent 왕복·스피커 출력 검증 수치가 아니다.

5. 왜 기존 IDLE을 그대로 완료로 쓰지 않는가

TaskManager가 있는데 왜 아직 실행 이식이 남았나?

작업을 기록하는 코드와 실제 작업을 수행시키는 연결은 다르기 때문이다. 현재 MR7의 Task 코어는 접수·대기열·상태 전이·취소 의도·이력 관리까지 분리되어 있다. 하지만 현재 TaskRequest에는 실행할 문장이나 이동 목적지 같은 도메인 인자가 없다. 따라서 이 객체를 앱 간 통신용으로 포장하는 것만으로 “말하기”나 “이동하기”가 실행되지는 않는다.

사용자가 기대하는 기능 현재 MR7 코어의 의미 다음 연결에서 필요한 일
“이 문장을 읽어 줘” 작업 이름·요청 식별자 등 메타데이터 접수 문장을 LLM 도메인에서 검증하고 AI Agent까지 전달
“제한 시간 안에 처리해 줘” timeoutMs 값 보관 실행 시간 제한 처리와 중단 결과 확인
“실패하면 다시 해 줘” maxRetries 값 보관 시도별 식별자·재시도 실행·이전 결과 차단
“취소해 줘” CANCELLING으로 취소 의도 표시 실제 실행 중단 요청과 결과 확인 후 최종 상태 판정
“내 작업만 보여 줘” 내부 기록 조회 실제 호출자 기준 접근 검사와 목록 제한

MR6 task_manager에는 실행 인자 전달과 실행 명령 호출 등 기존 경로가 있다. 이를 MR7에 옮길 때는 기능 인자는 해당 도메인, 작업 상태는 Task, 기존 Bundle 변환은 호환 계층으로 나눈다. MR6의 정책 우회 옵션이나 클라이언트가 보낸 source 문자열을 새 공개 API의 권한으로 인정하지 않는다.

공개 요청 방식은 도메인별 타입이 정해진 요청으로 접수하고, 공통 Task 계약으로 조회·취소하는 방식으로 사용자 승인을 받았다. 여기서 “타입이 정해진 요청”은 아무 문자열 키나 넣는 Bundle 대신 목적지·문장처럼 필수 항목이 명시된 요청 객체를 뜻한다. 구조 명세는 MR7 084e503에 기록했다. 방향 승인은 공개 API 구현 완료와 다르며, 실제 도메인 인자 전달·호출자별 접근 검사·Binder 연결은 구현과 검증을 거쳐야 한다.

소스 근거는 MR7 1fb0466의 docs/refactor/skix-task-binder-mr6-contract-audit-2026-09-09.md다. MR6 비교 기준은 task_manager의 d1d8dfbc4c807a32990f4350cf09c49f159f5848이다. 후속 기록 0c73b86에는 서버 295개·SDK 42개 JVM 테스트 강제 재실행 통과가 정리되어 있다. 이 수치는 이전 검증 기록이며, 실제 AI Agent 왕복·제품 부팅·스피커 출력 성공 수치가 아니다. 테스트에서 직접 성공 상태로 바꾸는 것 역시 물리 동작 완료의 증거로 세지 않는다.

이번 구현: 취소 요청의 이유와 실제 종료 결과를 분리

MR7 16b6b14에서 처음 접수한 취소 사유를 작업 조회 상태에 보존하도록 구현했다. 예를 들어 사용자가 취소한 이유는 cancellationReason=USER_REQUEST이고, 실행 담당자가 중단을 확인한 결과는 terminalReason=EXECUTOR_CONFIRMED다. 둘은 서로 덮어쓰지 않는다. 취소를 다시 요청해도 최초 사유를 유지한다.

위치 역할 이번 변경
base/core/java/.../framework/task/TaskSnapshot 작업 상태를 읽는 불변 공개 객체 취소 사유 조회 추가, 기존 생성자 유지
base/services/core/java/.../execution/task/TaskRecord 서버 내부 작업 기록 취소 사유와 종료 사유를 별도 저장
같은 폴더의 TaskManagerService 작업 상태를 변경하는 단일 책임자 기존 잠금 안에서 최초 취소 사유 반영

대기 중인 작업은 취소 요청 뒤 실행 대기열에서 빠지지만, CANCELLING은 실제 기기가 멈췄다는 뜻이 아니다. 실행 담당자의 중단 확인을 받아 최종 상태로 바꾸는 연결은 여전히 후속 작업이다. LLM·AMR에 별도 Task 상태 저장소를 추가하지 않았다.

검증은 서버 JVM 297개, SDK JVM 46개, 합계 343개 강제 재실행 통과와 프레임워크 폴더·레이어·공개 API 경계 검사 3종 통과다. 명세·품질 검토도 통과했다. APK 설치, Binder 왕복, 실제 주행 중단·음성 출력은 이번 검증에 포함되지 않는다.

후속 구현으로 도메인 실행 인자와 Task 접수를 함께 확정하는 내부 연결을 추가했다. 문장·목적지가 저장되기 전에 작업이 실행 대기열에서 꺼내지는 경합을 막는 단계다. MR7 d2ff0b0의 소스 분석에서는 기존 TTS 호출이 실행 거절 시 응답 없이 반환할 수 있고, AMR 이동 거절은 지연된 콜백으로 전달됨을 확인했다. 따라서 기존 메서드가 반환했다는 것만으로 새 Task를 실행 성공으로 표시하지 않는다.

실행 인자는 어디에 두고, 누가 꺼내 쓰는가?

접수 구조의 핵심은 도메인이 입력의 뜻을 알고, Task는 입력과 작업을 함께 관리한다는 것이다. 공통 Task에 TTS 문장 해석이나 목적지 좌표 보정을 넣지 않는다.

폴더·구성요소 맡는 일 맡지 않는 일
base/services/core/.../domain/ 기능별 입력 검증과 실행 담당자 연결 별도 공통 Task 상태 저장소 만들기
base/services/core/.../execution/task/TaskExecutionInput 도메인 소유 입력임을 나타내는 내부 계약 Bundle 키 해석, 장치 호출
같은 폴더의 TaskRecord 작업 기록과 입력 참조를 함께 보관 문장·좌표 내용 변경
같은 폴더의 TaskManagerService 하나의 잠금 안에서 접수·대기열 선택·상태 변경 실행 권한 부여, 물리 완료 추정
같은 폴더의 TaskExecutionClaim 선택 시점의 작업 상태와 타입을 확인한 입력을 함께 전달 최신 상태 보장, 취소 후 실행 허가

목표 흐름은 도메인에서 입력 준비 → Task와 함께 접수 → 담당 실행기가 타입을 확인해 선택 → 준비·취소·정책 조건 확인 → 실제 도메인 실행이다. 이 중 내부 접수·선택 연결과 실제 실행 연결은 서로 다른 완료 항목으로 관리한다. RUNNING으로 선택됐다는 사실만으로 기기가 움직였다고 보지 않는다. 입력의 불변성은 도메인 구현의 책임이며, 공통 인터페이스가 내부 필드를 자동으로 불변화하지는 않는다.

잘못된 종류의 입력을 요청하거나 기존 메타데이터 전용 방식으로 관리형 작업을 꺼내려 하면, 작업을 건너뛰거나 버리는 대신 거부하도록 한다. 종료 때 정리하는 것은 Task가 보유한 입력 참조다. 이미 실행 담당자에게 반환한 객체까지 회수하는 것은 아니므로, 실제 전송 직전의 검증은 별도로 필요하다.

내부 접수·선택, 잘못된 타입 거부, 대기열 순서, 취소·종료 정리, 동시 접근을 테스트했다. 시계 조회 실패 시 대기열에서만 작업이 빠지는 문제도 접수·선택·상태 전이·취소 경로에서 방지했다. 시계·ID 공급자가 같은 Task 서비스를 다시 호출해 기록을 바꾸는 재진입과 음수 시각도 거부한다. 강제 재실행 결과는 서버 314개 + SDK 46개 = 360개, 실패·오류·skip 0이며, release Java 컴파일과 구조 경계 검사 3종도 통과했다. 구현은 MR7 dda6bbf이며, 검증 근거는 docs/refactor/skix-atomic-execution-input-verification-2026-09-09.md다. 테스트의 발화·이동 입력은 테스트 전용 값이다. 실제 LLM/AMR 요청 연결, AI Agent 왕복, 제품 부팅·주행·음성 출력 검증을 완료한 것은 아니다.

전역 대기 상태와 해당 발화의 완료는 다르다

기존 AI Agent는 언어 설정이나 음성 성별 변경이 끝날 때도 IDLE을 전송한다. MR7의 LlmManager 콜백은 onChange(status, tts_type)이며 어떤 작업의 어떤 발화인지를 담지 않는다. 또 LlmCommandHandler.handleLlmStatus에는 조건부 청소 시작·주행 재개·IoT 통지가 함께 남아 있다.

따라서 기존 전역 IDLE에 현재 Task 키를 붙이면 다른 동작의 상태를 발화 완료로 오인할 수 있다. 전역 상태 정책을 새 큐에 복사하는 대신, 실행별 결과 전달과 도메인 간 제품 정책 분리를 따로 진행한다. 기존 코드에서 관찰한 동작은 유지·변경 여부를 특성 테스트와 사양으로 검토해야 하며, 이 문서만으로 기존 청소·주행 정책을 삭제하거나 변경하지 않는다.

MR6에 있던 TaskManager를 이식하는 작업인가?

맞다. 기존 TaskManager의 실행 기능을 MR7의 도메인 Manager/Service 구조로 이식하는 작업이다. 다만 공통 대기열을 만드는 것과 기존 실행기 전체를 연결하는 것은 구분해야 한다. 원본은 a1-packages-mr6의 task_manager 브랜치이며, 위에 명시한 revision을 기준으로 비교한다.

MR6 TaskExecutorBootstrap에 등록된 실행기와 옮겨 갈 책임은 다음과 같다. 오른쪽 열은 목표 책임 배치이며, 실행기별 이식 완료 표시가 아니다.

MR6 실행기 원본에서 맡은 일 MR7에서 나눠 맡을 곳
FrameworkTaskExecutor 배터리·펌웨어·기기/청정 상태, Launcher 화면 Power·Update·System·AirQuality·UI 등 해당 상태 소유자
StationLocationTaskExecutor 스테이션 위치 근거 AMR과 공통 근거 관리
DockingSignalProbeTaskExecutor 도킹 신호 탐색 AMR 실행·관측 연결
ObservationTaskExecutor 제자리 회전·시각 의미 관측 회전은 AMR, 시각 해석은 Vision
MainApiTaskExecutor 지도·Follow-me·보안·청정 옵션·휴일 AMR·Security·Cleaning·Schedule 등 기능별 분리
MovementTaskExecutor 이동·정지·복귀·도착 확인 AMR 실행, Cleaning 중단 확인, 공통 Task/정책/자원 협력
CleaningAmpTaskExecutor 청정/청소 동작과 중단 Cleaning 기능 책임
LlmTtsTaskExecutor 발화·음성 상태 변경·정지 LLM 연동, AI Agent 실행, Task 상태 관리
IotTaskExecutor 기기 상태·설정 전달 IoT 연결과 해당 설정 소유자
RoutineTaskExecutor 눈 LED 색·장면 UI/표현 도메인
ScheduleTaskExecutor 일정 추가·수정·조회·호출 Schedule 의미와 Task 실행 요청
UpdateTaskExecutor ARM/MCU OTA 결과·상태 Update 도메인과 MCU 연결 어댑터

registerSkeletonExecutors라는 원본 메서드 이름만 보고 모두 빈 구현이라고 판단하지 않는다. 또 MainApiTaskExecutor의 모든 기능을 새 공통 서비스 하나로 옮기는 것도 아니다. 서버 폴더 기준으로는 기능을 services/core/.../domain/, 작업 공통 관리를 services/core/.../execution/task/, 기존 명령 변환을 호환·어댑터 계층에 둔다.

이동 작업을 예로 보면, MR6에는 목적지 해석뿐 아니라 청소 중단 확인 → 이동 요청 → 도착 확인 → 구동부 IDLE 확인이 있다. 스테이션에 이미 올라가 있으면 복귀 명령을 다시 보내지 않는 분기도 있다. 단순히 좌표 객체를 새 Task에 붙이는 것만으로 이 동작들이 이식되지는 않는다.

발화 작업도, 문자열 선택 규칙과 완료 대기 여부가 따로 있다. 기존 tts → text → ttsText → message는 첫 non-null 값을 쓰므로 앞의 빈 문자열을 무시하지 않는다. 완료 대기 경로와 단순 전송 경로를 구분하고, 전역 IDLE을 새 실행의 완료로 추정하지 않아야 한다.

상세 소스 감사는 MR7 d103827의 docs/refactor/skix-mr6-executor-migration-map-2026-09-09.md에 기록했다. 원본 실행기·테스트를 읽은 결과이며, 이번에 MR6 테스트나 실제 기기 실행을 재현했다는 뜻은 아니다.

6. 폴더에서 찾기

상태가 바뀌면 누가 기기를 움직이는가?

상태 보고, 정책 판단, 실제 실행은 서로 다른 단계다. 예를 들어 AI Agent가 “발화가 끝났다”고 알렸다고 해서 LLM 서비스가 곧바로 주행·청소·틸팅을 모두 실행하는 구조로 만들지 않는다.

질문 맡길 책임 현재 이관 상태
음성 기능에서 무슨 일이 있었나? LLM 도메인이 AI Agent의 상태·결과를 해석 관측 모델과 실행별 완료 처리 내부 구현 존재
그 결과가 어느 작업에 해당하나? Task가 작업·시도와 결과를 연결 내부 계약 존재, 실제 생산자의 실행 키 반환은 후속
청소를 시작하거나 주행을 재개해도 되나? 해당 도메인 조건과 공통 정책·자원 조건을 함께 확인 기존 DeviceAgent의 제품 분기를 아직 대체하지 않음
실제로 어떻게 움직이나? AMR·Cleaning 등 담당 도메인을 통해 실행 구현에 요청 각 실행 경로의 단계별 이관 필요

기존 MR7의 LlmCommandHandler.handleLlmStatus()에는 음성 상태 갱신 뒤 자동 청소 시작, 조건부 재개, Follow-me 상태 변경, IoT 통지가 함께 들어 있다. 이것이 지금 분리하려는 혼합 책임이다. 청소 조건은 Cleaning, 주행 조건은 AMR이 소유하도록 나누되 기존 호출 순서와 예외 동작부터 고정한다. 모든 조건을 새 LlmManagerService 한 곳에 옮기는 것이 목표가 아니다.

왜 “LLM 사용 중”이라는 상태 하나로 통일하지 않는가?

MR6와 MR7 원본에서 이미 소비 목적별 의미가 달랐다. 아래는 콜백이 저장 상태를 바꾸지 않은 경우의 예다.

사례 관측에서의 의미 기존 제품 분기에서의 의미
상태 9: 명령 요청 중 파이프라인은 바쁨 기존 재개용 “음성 처리 중”에는 포함하지 않음
상태 10·11: 외부 TTS 음성 대화 세션은 아님 IoT에는 LLM 활성으로 보고
상태 0: IDLE 현재 대기 상태 실행 식별자 없이 특정 발화의 완료로 단정할 수 없음

따라서 pipelineBusy를 기존 재개 조건에 그대로 넣으면 동작이 바뀐다. 또 “음성 처리 중이 아님”만으로 재개할 수 있는 것도 아니다. 기존에는 PAUSE 상태와 호출어 조건도 확인한다.

구현 커밋 c960924: domain/llm/policy/LegacyLlmStatusPolicy.java에 잠금 중 상태 수신 허용, 기존 재개용 처리 상태, IoT 활성의 세 가지 순수 판정을 분리했다. 전용 JVM 테스트 5개를 포함해 당시 서버 40개 클래스·295개 테스트와 release 빌드를 통과했다. DeviceAgent 호출 연결·실시간 비교·실행 권한 전환은 아직 하지 않았다. 이 클래스는 입력을 분류할 뿐 청소·주행 명령을 내리지 않는다. 근거 분석은 docs/refactor/skix-llm-policy-migration-audit-2026-09-09.md에 누적 기록했다.

이후 무엇을 더 고정했나?

0bd43fe에서는 실제 DeviceAgent LlmManager를 호출하는 테스트 6개로 기존 동작을 고정했다. 특히 콜백 안에서 상태가 다시 바뀔 수 있으므로 받은 상태와 콜백 뒤 저장 상태는 다를 수 있다. 모르는 상태 99를 보고해도 저장값은 유지되고, IDLE 콜백이 예외를 던지면 뒤의 초기화가 실행되지 않는 동작도 확인했다. 이는 기존 행동을 기록한 것이지 그 행동이 최종 제품 사양으로 바람직하다고 확정한 것은 아니다.

b2b45fa에서는 AI Agent로 보내는 명령·데이터 키 등 문자열 29개의 정의를 base/compatibility/…/LlmBundleConstants.java로 모았다. 기존 5개를 재사용하고 24개를 추가했다. 보내는 메시지는 그대로이고, 그 이름을 관리하는 위치만 바뀌었다. 기존 호출자 이름은 별칭으로 유지하며 setTts와 setLlmTts를 임의로 합치지 않는다. 날씨·잠금·프라이버시 정책까지 LLM 서비스로 옮긴 것은 아니다.

이 후속 검증 당시 SDK 42개·DeviceAgent 22개 JVM 테스트와 DeviceAgent release 빌드가 통과했다. 실제 AI Agent 왕복과 기기 동작은 미검증이며, 이번 문서 배포에서 Android 테스트를 재실행한 것은 아니다. Android Log stub의 기존 stderr와 빌드 경고가 남아 있다. 독립 리뷰는 실행 환경의 에이전트 수 제한으로 수행하지 못했고 주 작업자가 원본과 대조했다.

후속 순서 정정 (70bf49c 소스 감사): 기존 설명은 실제 Android 호스트 연결을 선행 조건으로 드러내지 못했다. Task 전용 Binder 계약 → Android Runtime 호스트 → SystemServer 연결·제품 포함 → 실제 통신 위의 LLM 상태 비교 → 소유 도메인별 제품 조건 이관·실행별 Agent 결과 연결 → 종단 기기 검증 순으로 진행한다. 순수 상태 판정 테스트는 먼저 할 수 있지만, 그것만으로 프로세스 간 비교 연결이 구현되지는 않는다.

261f689에서는 송신 문자열 별칭의 공용 계약 참조도 검사기에 추가했다. 현재 기본 검사 범위는 9개 handler + LlmManager = 10개 소스이며, 문자열 값 검증과 소유 위치 검증은 별개다. 상세 범위와 한계는 도메인별 통신 계약에 기록한다.

아래 트리는 내부 관리 구현과 별도 앱의 실제 실행 구현을 구분한다.

packages-mr7-refacto/
├ skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/
│ ├ domain/llm/
│ │ ├ LlmManagerService.java               도메인 수명주기·선택 호스트 종료
│ │ ├ policy/LegacyLlmStatusPolicy.java    기존 상태 분류 구현·실행 연결 전
│ │ └ completion/
│ │   ├ TtsManagedHost.java                내부 연결 소유·큐 정리
│ │   ├ TtsObservationDelivery.java        기존 관측 전달 큐
│ │   └ TtsTaskCompletionService.java      기존 완료 해석
│ ├ control/capability/                    연결 세대·준비 상태
│ ├ control/resource/                      자원 소유권·재사용 확인
│ └ execution/task/                        Task 상태·완료 판정
├ skix-framework/base/compatibility/java/…/legacy/bundle/
│ └ LlmBundleConstants.java               AI Agent 통신 명령·키의 공용 정의
└ apps/DeviceAgent/.../llm/LlmManager.java  기존 전역 상태·요청, 공용 계약 참조

skmagic_ondeviceai_agent/                  별도 비교 저장소
└ app/src/main/java/com/skmagic/ondeviceai/agent/service/
  ├ ForegroundService.kt                  기존 음성 기능 실행 흐름
  └ DeviceCommunicator.kt                 기존 상태 전송

별도 비교 저장소의 확인 HEAD는 225ec738이다. 최종 MR7 생산자 브랜치로 확정했다는 의미는 아니다. MR7 상세 경로: apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/llm/LlmManager.java, apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/domain/interaction/LlmCommandHandler.java. 설계 근거: docs/superpowers/specs/2026-09-09-skix-tts-managed-host-design.md.

7. 연결이 끊기거나 서비스가 멈추면

내부 관리 객체는 새 입력 차단 → 해당 연결 폐기 → 대기 관측·완료 상관 상태 정리를 책임진다. 이미 전달 중인 호출은 소급 취소했다고 보고하지 않는다. 런타임은 LLM을 STOPPING으로 바꾼 뒤 onStop()을 호출하므로 종료 처리가 READY를 요구해서는 안 된다. 정확히 일치하는 연결만 폐기하는 closeProviderConnection()을 사용한다. LLM의 onStop()은 호스트 정리 뒤 finally에서 기존 완료 상관 상태도 정리한다.

호출 무엇을 정리하는가 이후 사용
closePlayback(...) 식별자가 일치하는 발화의 대기 관측·상관 상태 연결은 유지하고 다음 발화를 등록할 수 있음
disconnect(token) 그 연결과 발화의 대기 관측·상관 상태 명시적으로 새 연결을 열 수 있음
close() 호스트를 영구 종료하고 소유 연결·큐를 정리 같은 객체를 다시 열 수 없음. 재시작은 새 객체 구성

폐기한 대기 관측 수는 반환값으로 확인한다. updateReadiness(...)의 ReadinessResult는 준비 상태 반영 결과 update()와 폐기 수 dropped()를 함께 돌려준다. 이 수는 실패한 작업 수나 취소한 재생 수가 아니다. 이미 전달 중인 항목도 폐기 수에 넣지 않는다.

drain()은 명시적으로 호출하는 전달 동작이다. 바쁜 경우 같은 항목을 보존하지만 자동 재시도 타이머는 구현하지 않았다. 실제 Android 연결부에서 호출·재시도 시점을 결선해야 한다. 공통 stop(reason)의 상태 전환만으로 정리가 호출되는 것도 아니다. 레지스트리의 onStop() 경로와 구분한다.

작업 결과, 실제 장치 상태, 서비스 건강 상태를 하나의 상태값으로 합치지 않는 것이 핵심이다.

Connections

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date