SoC TaskManager Source-Level Implementation Guide
이 장은 SoC/DeviceAgent 업체가 실제 코드 기준으로 구현을 따라 할 수 있도록 정리한 소스 레벨 가이드다. 개념 설명은 Before/After Change Model을 보고, 실제 구현 위치와 method 단위 작업은 이 문서를 기준으로 본다.
1. 핵심 결론
TaskManager는 기존 domain 동작을 대체하는 별도 앱이 아니다. 기존 MainApi.executeMethod(...) 앞단에 공통 실행 layer를 넣고, 실제 기기 동작은 기존 executeMethodInternal(...) 또는 domain executor로 위임하는 구조다.
현재 코드에서 이 구조는 아래처럼 연결되어 있다.
MainApi.executeMethod(...)
-> TaskManager.handleControlMethod(...)
-> handled이면 TaskManager API 처리 후 return
-> 아니면 TaskManager.executeLegacy(...)
-> executeMethodInternal(...)로 기존 domain 기능 실행
근거:
apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/main/api/MainApi.java
import com.sk.airbot.deviceagent.task.TaskManager;
executeMethod(...)에서 TaskManager.handleControlMethod(...)를 먼저 호출
처리되지 않은 method는 executeLegacy(...)를 통해 executeMethodInternal(...)로 위임
2. 기존 진입점에 추가되는 Hook
기존 구조:
DeviceAgent
-> MainApi.executeMethod(method, indata, outdata)
-> executeMethodInternal(method, indata, outdata)
-> FrameworkCommandBridge 또는 기존 switch/handler
변경 구조:
DeviceAgent
-> MainApi.executeMethod(method, indata, outdata)
-> TaskManager.handleControlMethod(method, indata, outdata, factory)
-> submitTask / submitWorkflow / getTaskStatus / cancelTask 등 TaskManager API면 여기서 처리
-> TaskManager.executeLegacy(...)
-> 기존 executeMethodInternal(...) 호출 유지
업체 구현 포인트:
| 항목 | 구현 내용 |
|---|---|
| Hook 위치 | MainApi.executeMethod(...) 맨 앞에서 TaskManager control method를 먼저 검사 |
| 기존 동작 보존 | TaskManager가 처리하지 않는 method는 기존 executeMethodInternal(...)로 그대로 흘려보냄 |
| factory 위임 | TaskManager가 실제 command 실행이 필요할 때 기존 method를 호출할 수 있도록 TaskCommandFactory를 넘김 |
| 안전성 | TaskManager disable 시 기존 legacy 실행이 가능한 경로를 유지 |
3. TaskManager API Entry
TaskManager.java는 아래 control method를 공통 API로 제공해야 한다.
submitTask
submitWorkflow
getTaskStatus
listTasks
getQueueStatus
updateTaskProgress
cancelTask
getTaskManagerStatus
getDevicePlanningContext
classifyTaskIngress
현재 코드 기준 책임:
| 영역 | 코드 책임 |
|---|---|
| API dispatch | handleControlMethod(...)가 method별 처리 함수로 분기 |
| record 저장 | records, recordOrder, TaskRecord로 task lifecycle 추적 |
| queue 관리 | executors, queueMetrics, PriorityTaskExecutor로 queue 상태 관리 |
| event listener | eventListeners로 내부 listener 통지 |
| 외부 callback | notifyTaskEvent(...)에서 AppCmd.INSTANCE.sendModuleCallback_main(eventData) 호출 |
3.1 Control Method별 입력/출력
| method | 필수 입력 | 주요 출력 | 용도 |
|---|---|---|---|
submitTask |
taskMethod, source |
accepted, taskId, taskState, executionMode |
단일 task 등록 |
submitWorkflow |
workflowName, subTasks, source |
accepted, taskId, workflowName, subTaskStates |
복합명령 등록 |
getTaskStatus |
taskId |
task detail, input/result/reason | 특정 task 조회 |
listTasks |
optional filter | task summary list | task 목록 조회 |
getQueueStatus |
없음 또는 queue filter | queue별 running/pending/state | queue 상태 확인 |
updateTaskProgress |
taskId, progress/stage/message |
updated status | 외부 executor 진행률 반영 |
cancelTask |
taskId 또는 workflow id |
cancel accepted/result | 실행 중 task 취소 |
getTaskManagerStatus |
없음 | enabled, queue size, metrics | manager 상태 조회 |
getDevicePlanningContext |
없음 | device context bundle | 실행 전 planner/server context |
classifyTaskIngress |
method, optional signalType |
signal type, action | method 성격 분류 |
3.2 공통 Bundle Key 규칙
| 계열 | key |
|---|---|
| source | source, origin, caller |
| external trace | external_command_id, externalCommandId |
| MQTT trace | mqtt_message_id, mqttMessageId |
| App trace | app_request_id, appRequestId |
| Schedule trace | schedule_id, scheduleId |
| Cloud trace | cloud_workflow_id, cloudWorkflowId, cloud_step_id, cloudStepId, cloud_plan_id, cloudPlanId |
| task input | taskMethod, executionMode, queueKey, priority, timeoutMs, retry, cancellable, strictValidation |
| workflow input | workflowName, subTasks, currentStepIndex, currentStepMethod |
| progress | progress, stage, message |
3.3 최신 Task 등록 API 예시
단일 task는 method=submitTask, 실제 기능은 taskMethod에 넣는다. 기존 기능 method를 직접 호출하지 않고 TaskManager admission/policy/queue를 통과시키려면 forceTaskManager=true를 함께 둔다.
forceTaskManager = true
method = "submitTask"
taskMethod = "setMoveTo"
source = "IOT"
executionMode = "async"
targetRoom = "안방"
areaId = "5"
positionId = "5"
timeoutMs = "240000"
복합 task는 method=submitWorkflow와 subTasks를 사용한다. 아래 예시는 실기기 검증 기준 5명령 workflow다.
forceTaskManager = true
method = "submitWorkflow"
workflowName = "bedroom_livingroom_clean_return"
source = "IOT"
executionMode = "queued_wait"
subTasks = [
{ taskMethod = "setMoveTo", label = "안방 이동", targetRoom = "안방", areaId = "5", positionId = "5", timeoutMs = "240000" },
{ taskMethod = "startBasicAirClear", label = "안방 청정", action = "1", mode = "0", completionTarget = "cleaning.stopped", cleanMinTimeMs = "60000", cleanMaxTimeMs = "90000", timeoutMs = "180000" },
{ taskMethod = "setMoveTo", label = "거실 이동", targetRoom = "거실", areaId = "6", positionId = "6", timeoutMs = "240000" },
{ taskMethod = "startBasicAirClear", label = "거실 청정", action = "1", mode = "0", completionTarget = "cleaning.stopped", cleanMinTimeMs = "60000", cleanMaxTimeMs = "90000", timeoutMs = "180000" },
{ taskMethod = "returnToStation", label = "스테이션 복귀", timeoutMs = "240000" }
]
완료 기준:
| taskMethod | 완료 기준 |
|---|---|
setMoveTo |
movement.arrived(areaId) 관측 후 이동 idle |
startBasicAirClear |
cleanMinTimeMs 경과 후 stop, cleaning.stopped 관측 |
returnToStation |
movement.stationCharging 관측 |
4. submitTask 처리 상세
submitTask는 단일 실행 요청의 공통 admission gate다.
흐름:
enabled check
-> taskMethod 필수 확인
-> TaskBundleValidator.validateSubmitTask(...)
-> copyForTask(...)
-> TaskPolicyRegistry.resolve(...)
-> TaskSource.from(source)
-> caller policy check
-> state condition check
-> resource policy check
-> createTaskCommand(...)
-> DIRECT / QUEUED_WAIT / ASYNC 실행
업체 구현 의미:
| 단계 | 의미 |
|---|---|
| enabled check | property 또는 bundle flag로 TaskManager 적용 여부 제어 |
| validation | method별 필수 slot 누락을 execution 전에 차단 |
| policy resolve | method별 queue, timeout, retry, cancellable, cancel method 결정 |
| source parse | Cloud/MQTT/App/예약 등 유입원을 구분 |
| state/resource check | 배터리, 이동 중, cleaning transaction, queue busy 같은 runtime 조건 반영 |
| command creation | 기존 domain handler 또는 새 executor로 실제 실행 위임 |
5. submitWorkflow 처리 상세
submitWorkflow는 복합명령을 위한 핵심이다. 단순히 여러 method를 한 번에 보내는 것이 아니라, workflow 자체를 task로 만들고 subTask 상태를 추적한다.
흐름:
subTasks 필수 확인
-> workflow policy 생성
-> workflow TaskRecord 생성
-> 각 subTask 순차 실행
-> parallelGroup이면 병렬 그룹 실행
-> currentStepIndex / currentStepMethod 갱신
-> WORKFLOW_STEP_STARTED event
-> subCommand.run()
-> WORKFLOW_STEP_COMPLETED event
-> subTaskResults / subTaskStates 결과 기록
예시:
submitWorkflow
workflowName = "bedroom_livingroom_clean_return"
subTasks[0] = { taskMethod: "setMoveTo", targetRoom: "안방", areaId: "5" }
subTasks[1] = { taskMethod: "startBasicAirClear", action: "1", mode: "0", completionTarget: "cleaning.stopped" }
subTasks[2] = { taskMethod: "setMoveTo", targetRoom: "거실", areaId: "6" }
subTasks[3] = { taskMethod: "startBasicAirClear", action: "1", mode: "0", completionTarget: "cleaning.stopped" }
subTasks[4] = { taskMethod: "returnToStation" }
업체 구현 포인트:
| 항목 | 요구 |
|---|---|
| 순차 보장 | 앞 step이 끝나기 전 다음 step의 runtime 조건을 확정하지 않음 |
| step event | 각 step 시작/완료를 WORKFLOW_STEP_STARTED, WORKFLOW_STEP_COMPLETED로 알림 |
| 실패 처리 | step 실패 시 parent workflow에 reason을 기록하고 terminal event 전송 |
| 결과 기록 | subTaskResults, subTaskStates를 결과 bundle에 포함 |
| parallelGroup | 안전성 검토 후 허용 domain만 병렬 실행 |
5.1 Workflow에서 실행 시점 조건 판단
복합명령에서 중요한 점은 “workflow 등록 시점”과 “subTask 실행 시점”의 기기 상태가 다를 수 있다는 것이다.
예:
1. 거실로 이동
2. 거실에서 청정 시작
3. 안방으로 이동
이 경우 2번 청정 조건은 workflow 등록 시점이 아니라 1번 이동 완료 후 확인해야 한다. 따라서 업체 구현은 아래 원칙을 따른다.
| 원칙 | 설명 |
|---|---|
| registration-time validation | taskMethod와 필수 slot이 있는지만 확인 |
| execution-time condition | battery, map, movement, cleaning transaction, room availability는 step 실행 직전에 확인 |
| step-local failure | 실패한 step의 currentStepIndex, currentStepMethod, reason_code를 event에 남김 |
| parent workflow terminal | subTask 실패가 parent workflow terminal event로 이어져야 함 |
5.2 Workflow Event 순서
정상 순차 workflow:
ACCEPTED
RUNNING
WORKFLOW_STEP_STARTED(index=0)
WORKFLOW_STEP_COMPLETED(index=0)
WORKFLOW_STEP_STARTED(index=1)
WORKFLOW_STEP_COMPLETED(index=1)
WORKFLOW_STEP_STARTED(index=2)
WORKFLOW_STEP_COMPLETED(index=2)
COMPLETED or WORKFLOW_COMPLETED
실패 workflow:
ACCEPTED
RUNNING
WORKFLOW_STEP_STARTED(index=0)
WORKFLOW_STEP_COMPLETED(index=0)
WORKFLOW_STEP_STARTED(index=1)
FAILED(reason_code=PATH_BLOCKED or STATE_CONDITION_FAILED)
6. Policy Registry
TaskPolicyRegistry.java는 method별 실행 정책을 정의한다.
현재 주요 queue:
emergency
movement
cleaning
state
update
sound
settings
ai
default
대표 정책:
| method | mode | queue | timeout | cancellable |
|---|---|---|---|---|
getBatteryInfo |
DIRECT |
device_info |
5s | false |
getFirmwareVersion |
DIRECT |
device_info |
5s | false |
setMainState |
QUEUED_WAIT |
state |
default | false |
returnToStation |
ASYNC |
movement |
120s | true |
setMoveTo |
ASYNC |
movement |
120s | true |
setAirCleanerOperation |
ASYNC |
cleaning |
1200s | true |
setStatusClean |
QUEUED_WAIT |
cleaning |
default | true |
setChangeLlmStatus |
ASYNC |
ai |
default | true |
setConfig |
QUEUED_WAIT |
settings |
default | false |
업체 구현 포인트:
- get/is/info/version/dump 성격은 기본적으로
DIRECT후보로 둔다. - 이동/청정/AI/TTS처럼 시간이 걸리는 제어는
ASYNC또는QUEUED_WAIT로 둔다. - stop/emergency/error/reset 계열은 emergency 성격으로 우선순위를 높인다.
- cancel/compensation method는 queue별 default를 제공한다.
7. Bundle Validation
TaskBundleValidator.java는 strict validation이 켜졌을 때 method별 필수 입력을 검사한다.
현재 schema 예시:
| method | 필수 후보 key |
|---|---|
setMoveTo |
positionId, positionName, position, positionIds |
setMoving |
action, operation, moving |
setAirCleanerOperation |
action, mode, speed, operation |
setStatusClean |
status, cleanStatus, value |
setChangeLlmStatus |
status, llmStatus, action |
setLlmTts |
text, ttsText, message |
setConfig |
key, configKey, name |
주의:
- 이 validation은 domain safety logic을 대체하지 않는다.
- 기능 내부 조건 판단은 기존 handler/domain manager가 계속 수행한다.
- validation의 목적은 명백한 slot 누락을 execution 전에 차단하는 것이다.
8. Executor Binding
TaskExecutorBootstrap.java는 task method와 executor를 연결한다.
현재 skeleton binding:
| domain | methods |
|---|---|
| Movement | returnToStation, setMoveTo, setMoving, stopMovement |
| Cleaning | setAirCleanerOperation, setStatusClean, stopCleaning, ampStop |
| LLM/TTS | setChangeLlmStatus, setLlmTts, stopLlm, stopTts |
| IoT | setDeviceStatus, setConfig, getConfig |
| Update | OTAUpdateArmResult, OTAUpdateMcuResult, setFirmwareUpdateStatus |
업체가 해야 할 일:
- skeleton executor를 실제 domain manager 호출로 연결한다.
- dry-run executor와 real executor를 구분한다.
- long-running 동작은 progress update 또는 event callback을 보장한다.
- cancel method가 있는 domain은 stop/cancel path까지 연결한다.
- domain 실패를
TaskReasonContract가 이해 가능한 error code로 넘긴다.
8.1 Domain Adapter 작성 패턴
TaskManager executor는 가능하면 얇은 adapter로 유지한다.
TaskExecutor
-> input Bundle normalize
-> 기존 domain handler 호출
-> domain 결과/error를 task output 또는 exception/errorCode로 변환
청정 예시:
taskMethod = setAirCleanerOperation
-> CleaningAmpTaskExecutor
-> CleanOperationCommandHandler.handleSetAirCleanerOperation(...)
-> CleaningTransaction / PolicyGate / Executor
-> success or domain error
이동 예시:
taskMethod = setMoveTo
-> MovementTaskExecutor
-> MovingController / RobotMovementController / map location lookup
-> success, PATH_BLOCKED, ROOM_NOT_FOUND 등
금지할 구현:
- TaskManager executor 안에 청정/이동 business rule을 중복 구현
- source가 cloud인지 mqtt인지에 따라 domain 동작 자체를 다르게 구현
- event를 보내지 않고 domain method만 호출
- 실패를 exception message나 toast string으로만 남김
9. 기존 Domain Handler와의 관계
예를 들어 setAirCleanerOperation은 기존 CleanOperationCommandHandler에서 이미 많은 조건 판단을 수행한다.
근거:
apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/framework/command/CleanOperationCommandHandler.java
handleSetAirCleanerOperation(...)
AppCmd.INSTANCE.sendModuleCommand_iot(indata)
Settings lock mode 확인
InputNormalizer.normalize(...)
PolicyGate.shouldSkipCurrentStep(...)
PolicyGate.applyBeforeExecution(...)
Executor.execute(...)
따라서 TaskManager가 해야 하는 일과 domain handler가 해야 하는 일을 분리해야 한다.
| 계층 | 해야 할 일 | 하지 말아야 할 일 |
|---|---|---|
| TaskManager | task admission, queue, workflow, event, reason, source trace | 청정 세부 정책을 모두 재구현 |
| Domain Handler | 기존 기능별 조건 판단, 실제 SoC command, sensor/transaction 판단 | Cloud/MQTT/App source별 workflow 상태 관리 |
| Executor Adapter | TaskManager task를 기존 handler 호출로 연결 | business logic 중복 구현 |
9.1 기존 제품 정책 계승 경로
실행기는 기존 기능 메서드만 재사용하는 것이 아니라 그 메서드 앞뒤에 연결된 제품 정책까지 계승해야 한다. 기준 구조와 정책 행렬은 기준 사양 6.7에 둔다.
TaskManager 공통 Admission
-> FrameworkCommandBridge 또는 승인된 Domain Adapter
-> PolicyEngine / StateManager / CmdPolicyManager
-> 기존 Domain Manager와 Controller
-> HAL·MCU·AMR
-> Domain callback과 CompletionStateStore
이동의 경우 MovementTaskExecutor가 이동 좌표만 처리하고, 실제 허용 판정은 MovementCommandHandler -> PolicyEngine -> CmdPolicyManager 경로가 맡는다. 이동이 시작된 뒤에는 MovingLocationManager가 MOVING 틸트 조건과 LCD 깨움을 적용하고, TiltingManager가 LLM·관찰·도킹 조건을 합성해 최종 자세를 결정한다.
따라서 구현 검토에서는 다음을 확인한다.
- Task 경로가 기존 도메인 정책 진입점을 건너뛰지 않는가
- 기존 경로의 거절을 Task 성공으로 잘못 변환하지 않는가
- 틸트·화면·팬처럼 내부 부수 정책을 Workflow 작성자에게 전가하지 않는가
- 도메인 자동 전환과 완료 증거가 Task 이벤트에 남는가
- 기존 호출과 관리 호출이 같은 상태에서 같은 허용 결과를 내는가
10. Reason Contract
TaskReasonContract.java는 내부 error를 상위가 이해 가능한 reason으로 정규화한다.
대표 mapping:
| 내부 error 후보 | 표준 reason |
|---|---|
TASK_TIMEOUT, TIMEOUT |
TIMEOUT |
CANCELLED, USER_CANCELLED |
USER_CANCELLED |
QUEUE_FULL, RESOURCE_BUSY, DEVICE_BUSY |
DEVICE_BUSY |
CPU_LIMIT, RAM_LIMIT, THERMAL_LIMIT |
RESOURCE_LIMIT |
BLOCKED_BY_STATE, STATE_CONDITION_FAILED |
STATE_CONDITION_FAILED |
PATH_BLOCKED_BY_OBSTACLE, NAVIGATION_PATH_BLOCKED |
PATH_BLOCKED |
POSITION_NOT_FOUND, LOCATION_NOT_FOUND, TARGET_NOT_FOUND |
ROOM_NOT_FOUND |
BATTERY_LOW |
LOW_BATTERY |
SENSOR_FAILURE |
SENSOR_ERROR |
후처리 필드:
| field | 의미 |
|---|---|
reason_code |
표준 실패 이유 |
reason_params |
위치/방/대상 등 구조화 parameter |
recoverability |
device_self_recoverable, user_action_required, auto_retryable, cloud_replan_required |
suggested_action |
DOCK_AND_RESUME, ASK_USER_CLEAR_PATH, ASK_USER_TARGET, RETRY_OR_WAIT, REPLAN 등 |
requires_cloud_decision |
Cloud 판단이 필요한 실패인지 여부 |
중요:
LOW_BATTERY,DEVICE_BUSY,RESOURCE_LIMIT은 기본적으로 Cloud LLM 재판단 없이 device/server policy로 처리 가능하다.PATH_BLOCKED,ROOM_NOT_FOUND,STATE_CONDITION_FAILED,SENSOR_ERROR등은 사용자 질문 또는 replan 후보가 된다.- MQTT/App source에서는
requires_cloud_decision을 그대로 Cloud 호출로 해석하지 말고, server report/user notification 정책과 분리해서 사용한다.
11. Planning Context
DevicePlanningContextProvider.java는 실행 전 상위 planner/server가 참고할 수 있는 snapshot을 만든다.
현재 snapshot field:
schema_version = device_context.v1
snapshot_ts
updated_at_ms
main_state
battery
map
location
cleaning
movement
task_manager
capabilities
세부 내용:
| bundle | 주요 field |
|---|---|
battery |
available, percent, is_low, is_charging, raw_capacity |
map |
available, editable, rooms |
location |
current_room_id, current_room_name, is_on_station |
cleaning |
is_running, is_paused, last_action, area_info |
movement |
moving_status, is_moving, is_paused, blocked |
task_manager |
queue_status, busy, running_tasks, queue_summary |
capabilities |
movement, room_cleaning, return_to_station, tts, schedule |
업체 구현 포인트:
rooms는 실제 map/room 정보를 채워야 한다.current_room_id/name은 가능한 경우 localization 결과로 채운다.blocked는 이동 controller가 제공하는 장애물/path 상태와 연결해야 한다.- capability는 제품/펌웨어/설정에 따라 false가 될 수 있어야 한다.
12. Ingress Classification
TaskIngressClassifier.java는 method를 task/control/progress/result/sensor/event/trigger로 분류한다.
분류 결과는 아래 용도로 쓴다.
| signal type | 의미 | action |
|---|---|---|
COMMAND_TASK |
실행 command | submit_task 후보 |
TASK_CONTROL |
TaskManager API | task_manager_api |
PROGRESS_UPDATE |
진행률 update | update_task_progress |
RESULT_UPDATE |
결과 callback | update_task_result |
SENSOR_DATA |
sensor/status 데이터 | state_or_resource_update |
TRIGGER |
schedule/alarm/trigger | policy check 후 task 생성 가능 |
EVENT_CALLBACK |
callback/event | event callback 처리 |
업체 구현 포인트:
- MQTT 서버에서 내려온 method도 이 classifier를 통과시키면 task로 넣을지, progress/result로 볼지 일관되게 판단할 수 있다.
- 예약/알람은 곧바로 실행하기보다 trigger로 분류한 뒤 policy를 거쳐 task 생성하는 편이 안전하다.
12.1 Source별 정책 분기
| source | 정상 event | 실패 event | Cloud LLM 호출 여부 |
|---|---|---|---|
cloud_a2a |
On-device 상태/notification 갱신 | requires_cloud_decision=true면 planner replan 후보 |
판단 필요 실패만 |
mqtt_server |
서버 ack/status/report | server failure report, retry, user notification | 기본적으로 호출하지 않음 |
app_remote |
앱 UI state 갱신 | 앱에 reason code와 조치 안내 | 기본적으로 호출하지 않음 |
local_voice |
로컬 TTS/notification | 사용자에게 재시도/불가 안내 | 필요 시만 |
internal_schedule |
내부 log/status | retry/cancel/protection policy | 호출하지 않음 |
13. Event Payload Contract
TaskManager event는 notifyTaskEvent(...)에서 생성된다.
공통 흐름:
record.writeSummaryTo(eventData)
eventData.method = "onTaskEvent"
eventData.eventName = eventName
listener.onTaskEvent(eventName, eventData)
AppCmd.INSTANCE.sendModuleCallback_main(eventData)
event에 반드시 남겨야 할 계열:
| 계열 | field 예시 |
|---|---|
| task identity | taskId, taskMethod, workflowName |
| state | taskState, status, progress, stage, message |
| workflow | currentStepIndex, currentStepMethod, subTaskStates |
| source trace | source, origin, caller, external_command_id, mqtt_message_id, app_request_id, schedule_id |
| cloud trace | cloud_workflow_id, cloud_step_id, cloud_plan_id, cloud_output_key |
| reason | reason_code, reason_params, recoverability, suggested_action, requires_cloud_decision |
14. 업체 구현 순서
권장 구현 순서:
MainApi.executeMethod(...)앞단에 TaskManager hook을 붙인다.submitTask,submitWorkflow, query/cancel/status API를 구현한다.TaskPolicyRegistry에 method별 queue/mode/timeout/cancel 정책을 채운다.TaskBundleValidator에 필수 slot schema를 채운다.TaskExecutorRegistry에 기존 domain handler adapter를 연결한다.TaskReasonContract에 domain error mapping을 추가한다.DevicePlanningContextProvider에 실제 map/location/movement/cleaning/battery 상태를 채운다.onTaskEventpayload를 Cloud/MQTT/App/예약 source별 trace와 함께 검증한다.- unit/integration/instrumented/logcat evidence를 제출한다.
15. 구현 완료 기준
업체 구현은 아래가 모두 확인되어야 한다.
| 기준 | 완료 증거 |
|---|---|
| API 동작 | submitTask, submitWorkflow, getTaskStatus, cancelTask sample bundle |
| legacy 보존 | TaskManager가 처리하지 않는 기존 method가 그대로 동작하는 trace |
| workflow | step별 WORKFLOW_STEP_STARTED, WORKFLOW_STEP_COMPLETED, terminal event trace |
| reason | 실패 case별 reason_code, reason_params, recoverability trace |
| source trace | Cloud/MQTT/App/예약 source id가 event까지 보존되는 trace |
| planning context | getDevicePlanningContext response sample |
| 실기기 | ADB/logcat 또는 업체 장비 trace |
15.1 2026-07-23 A1 실기기 확인 결과
현재 A1 실기기에서 원복된 기존 배포 APK는 서비스 안정성은 확인됐지만, TaskManager API surface는 아직 열려 있지 않다.
확인 조건:
device = 192.168.123.116:5555
DeviceAgent = com.sk.airbot.deviceagent / versionName 0.249
MaumAi = com.skmagic.ondeviceai.agent / versionName 1.19.28
MaumAi ForegroundService = isForeground=true
확인 결과:
dumpsys activity service com.sk.airbot.deviceagent/.DeviceAgent method=getDevicePlanningContext
-> dump outdata : Bundle[{}]
dumpsys activity service com.sk.airbot.deviceagent/.DeviceAgent method=getTaskManagerStatus
-> dump outdata : Bundle[{}]
해석:
- 기존 배포 APK는 부팅과 MaumAi 서비스 기동에는 문제가 없지만
getDevicePlanningContext,getTaskManagerStatus의 실제 응답이 비어 있다. - 따라서 기존 APK만으로는 Cloud A2A -> On-device Bridge -> DeviceAgent TaskManager
submitWorkflowE2E를 완료할 수 없다. - 실제품 검증은 TaskManager hook/API가 포함된 DeviceAgent 소스를 실제 단말 버전과 맞춘 뒤 platform-signed privileged APK로만 진행해야 한다.
dumpsys는 문자열 Bundle로 들어가는 값이 많아forceTaskManager=true,executorDryRun=true같은 boolean 계약 검증에 부적합하다. 실제 workflow 제출 검증은 On-device Bridge/AAR/SDK 같은 typed Bundle caller로 수행해야 한다./system/priv-app교체 전에는skmagic_ondeviceai_agent/scripts/real_device_apk_preflight.sh로 candidate APK와 backup APK의 signing SHA-256을 비교해야 한다. Android debug certificate가 감지되면 배포를 중단한다.- Cloud repo의
device_e2e_runner는getTaskManagerStatus가 실제 payload를 반환하지 않으면task_manager_status_unavailable에서 중단해야 한다. 기존 0.249 APK처럼 dumpsys returncode만 성공이고 outdata가 비어 있는 상태에서는submitTask를 실행하지 않는다. device_task_dry_run --run submit-cleaning은--allow-submit없이는 실행되지 않아야 한다. dry-run 출력은 명령 확인용이고, 실기기 실행 승인은 별도 단계로 둔다.
Cloud/On-device 계약 보강:
| field | 방향 | 의미 |
|---|---|---|
orchestration.device_workflow_requests |
Cloud -> On-device | submitWorkflow로 바로 넘길 수 있는 TaskManager workflow 요청 |
orchestration.device_workflow_blocked |
Cloud -> On-device | workflow를 만들 수 없었던 이유. 예: ROOM_CONTEXT_REQUIRED, ROOM_TARGET_NOT_FOUND |
device_workflow_blocked.required_targets |
Cloud -> On-device | room id 해석이 필요했던 대상 이름 |
device_workflow_blocked.available_rooms |
Cloud -> On-device | 현재 context에 있던 room 이름 목록 |
device_workflow_blocked.recoverability |
Cloud -> On-device | refresh_device_context, ask_user_target_or_refresh_map 등 복구 방향 |
이 필드는 실패를 숨기지 않기 위한 진단 계약이다. Cloud가 room name만 가진 multi-step device workflow를 만들었는데 DeviceAgent planning context가 없거나 target room을 찾지 못하면, 조용히 device_workflow_requests를 생략하지 않고 device_workflow_blocked를 함께 내려야 한다.
TaskManager callback ingress:
| field | 방향 | 의미 |
|---|---|---|
type=task_event |
DeviceAgent/Monitor -> Cloud | TaskMonitorReporter가 직접 올리는 event envelope |
request_kind=task_event |
On-device Bridge -> Cloud | bridge가 명시적으로 감싼 task event envelope |
eventName |
DeviceAgent/Monitor -> Cloud | WORKFLOW_STEP_COMPLETED 같은 lifecycle event 이름. status보다 event delta에서 우선한다. |
status |
DeviceAgent/Monitor -> Cloud | 현재 task 상태. 예: RUNNING, COMPLETED, FAILED |
currentStepIndex, currentStepMethod |
DeviceAgent/Monitor -> Cloud | workflow 내부 어느 step이 진행/완료됐는지 표시 |
cloudWorkflowId, cloudStepId, cloudPlanId |
DeviceAgent/Monitor -> Cloud | Cloud planner가 만든 workflow/step/plan trace |
requiresCloudDecision |
DeviceAgent/Monitor -> Cloud | 다음 step 전 Cloud 재판단이 필요한지 표시 |
Cloud Lambda는 두 envelope를 모두 받아야 한다. 즉 {"request_kind":"task_event","task_event":{...}} 형태뿐 아니라 TaskMonitorReporter가 직접 보내는 {"type":"task_event", ...} 형태도 request_kind=task_event로 정규화한다. 이때 status=RUNNING과 eventName=WORKFLOW_STEP_COMPLETED가 함께 오면 eventName은 workflow delta의 이벤트 종류로 유지하고, status는 현재 task 상태 계산에만 사용한다. 이렇게 해야 monitor timeline의 step 완료 신호와 Cloud workflow state가 어긋나지 않는다.
16. 최소 테스트 시나리오
| ID | 시나리오 | 기대 결과 |
|---|---|---|
| T-01 | submitTask(source=cloud_a2a, taskMethod=setAirCleanerOperation) |
accepted 후 terminal event, cloud trace 보존 |
| T-02 | submitTask(source=mqtt_server, taskMethod=setMoveTo) |
mqtt trace 보존, server report 가능한 event |
| T-03 | submitWorkflow 3-step 이동/청정/이동 |
step event 순서와 parent terminal event |
| T-04 | strictValidation=true에서 setMoveTo 위치 누락 |
rejected 또는 validation failure |
| T-05 | 이동 중 path blocked | reason_code=PATH_BLOCKED, suggested_action=ASK_USER_CLEAR_PATH |
| T-06 | low battery | reason_code=LOW_BATTERY, device self recoverable |
| T-07 | 기존 legacy method 호출 | TaskManager 미처리 후 기존 executeMethodInternal path 동작 |
| T-08 | getDevicePlanningContext |
battery/map/location/cleaning/movement/task_manager/capabilities 포함 |