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 |
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 = "living_room_clean_then_bedroom_move"
subTasks[0] = { taskMethod: "setMoveTo", positionName: "거실" }
subTasks[1] = { taskMethod: "setAirCleanerOperation", action: 1, mode: 2 }
subTasks[2] = { taskMethod: "setMoveTo", positionName: "안방" }
업체 구현 포인트:
| 항목 | 요구 |
|---|---|
| 순차 보장 | 앞 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 |
60s | 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 중복 구현 |
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 |
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 포함 |