← Docs hub

A2A Planner Deep Dive

이 문서는 feature_a2a_task_orchestration_cloud 브랜치 기준 Cloud A2A Planner의 실제 판단 구조를 정리한다. 기존 Planner and Routing은 큰 개념을 설명하지만, 현재 소스에는 preclassifier, ranked flow, object typer, replyability judge, second pass, active workflow short-circuit, task orchestration 연결까지 여러 판단 레이어가 있다.

A2A Planner Deep Dive

0. 빠른 이해

이 문서를 처음 보는 사람에게는 아래 한 문장으로 시작하면 된다.

Planner는 사용자 말을 바로 Agent에 던지는 장치가 아니라, “무슨 일인지 판단하고, 어떤 Agent가 어떤 순서로 처리할지 계획하며, 애매하면 한 번 더 확인하고, 실행 중 문제가 생기면 다시 계획하는 감독관”이다.

A2A Planner Simple View

0.1 가장 쉬운 비유

식당 주문으로 비유하면 다음과 같다.

A2A 구성 식당 비유 실제 역할
recognized_text 손님이 말한 주문 현재 사용자 발화
preclassifier 주문에서 “음료/식사/포장/알레르기” 표시하는 직원 판단 증거 플래그 생성
catalog/registry 가능한 메뉴판과 조리 가능 장비 지원 기능, Agent/Skill 목록
planner 주문을 주방/바/계산대로 나누는 매니저 route family와 step 계획 생성
second pass 애매한 주문을 매니저가 다시 확인 DEF/STT_NULL/FRG 회색지대 재판단
runtime/orchestrator 주방에 실제 조리 지시를 보내는 시스템 Agent 실행과 device task 변환
workflow_context 아직 끝나지 않은 주문표 멀티턴/복합 task 상태 저장

0.2 한 발화가 지나가는 쉬운 흐름

예시 발화:

거실로 가서 공기 상태 확인하고 필요하면 청정해줘

사람에게 설명할 때는 아래 순서로 말하면 된다.

  1. 먼저 발화에서 거실, 공기 상태, 청정해줘 같은 증거를 찾는다.
  2. 우리 기기가 실제로 이동/상태조회/청정을 지원하는지 registry에서 확인한다.
  3. planner가 “이건 ODL device 작업이고 순서가 있다”고 판단한다.
  4. move -> state check -> clean if needed 같은 step plan을 만든다.
  5. runtime이 device step을 온디바이스가 실행 가능한 task request로 바꾼다.
  6. 정상 진행은 기기에서 계속 처리하고, 실패/차단/추가 판단이 필요할 때만 Cloud가 다시 개입한다.

0.3 처음 읽는 사람에게 먼저 보여줄 5개 질문

질문 보면 되는 장
입력으로 뭐가 들어오나? 3. 입력 레이어
증거 플래그는 누가 만드나? 5. Preclassifier 생성 과정
Agent 후보는 어떻게 좁히나? 6. Candidate Pool과 Ranked Flow
최종 계획은 누가 만드나? 9. Main Planner LLM
실행 중 막히면 어떻게 하나? 12. Multi-step Plan과 Task Orchestration, 18. 남은 정리 과제

1. Planner의 실제 역할

1.1 설명용 예시

예시 발화:

오늘 실내 공기 상태 확인하고, 나쁘면 청정해줘

이 발화에서 planner가 하는 일은 “ODL 하나 선택”이 아니다. 먼저 실내 공기 상태 확인은 상태 조회 step이고, 나쁘면 청정은 앞 결과에 의존하는 실행 step이라는 구조를 만든다. 그래서 planner는 단순 selector가 아니라 “조건과 순서를 가진 작업 계획자”로 봐야 한다.

Planner는 단순히 ODL, SCH, DEF 같은 family 하나를 고르는 selector가 아니다. 현재 구현에서는 아래 일을 한 번에 수행한다.

2. 주요 소스

2.1 소스 읽는 순서 예시

처음 소스를 보는 사람은 모든 파일을 동시에 보면 어렵다. 아래 순서가 가장 덜 헷갈린다.

  1. main_router_api.py에서 route_turn(...)의 큰 흐름을 본다.
  2. preclassifier.py에서 어떤 evidence가 만들어지는지 본다.
  3. main_router.system_prompt.txt에서 LLM에게 어떤 판단 기준을 주는지 본다.
  4. orchestrator.pytask_manager.py에서 planner 결과가 실제 실행 payload로 바뀌는 부분을 본다.
영역 파일 역할
Planner entry gemini/a2a/planner/main_router_api.py route_turn(...), 후보군 구성, LLM planner 호출, 2차 judge, ranked flow 보정
Evidence layer gemini/a2a/planner/preclassifier.py 발화에서 capability/public/schedule/conversation/STT_NULL 증거 플래그 생성
Main prompt gemini/a2a/planner/assets/main_router.system_prompt.txt family, turn mode, steps 판단 기준
Main schema gemini/a2a/planner/assets/main_router.response_schema.json planner output JSON 계약
Reply object typer reply_object_typer.system_prompt.txt 첫 useful reply의 대상 타입 판단
Replyability judge replyability_judge.system_prompt.txt 지금 bounded reply가 가능한지 판단
Second pass judge second_pass_family_judge.system_prompt.txt DEF/STT_NULL/FRG 회색지대 pairwise 판단
Ranked flow gemini/a2a/planner/flow_selection_core.py catalog 기반 flow 후보와 allowed family prior 생성
Runtime gemini/a2a/runtime/orchestrator.py planner result를 agent execution/session_state/orchestration payload로 변환
Task manager bridge gemini/a2a/runtime/task_manager.py planner step을 device_task_requests로 변환

3. 입력 레이어

3.0 입력 레이어 예시

예시 발화:

매일 오후 6시에 고정청정 예약해줘

planner에는 단순히 이 문장만 들어가지 않는다. 대략 아래 묶음이 같이 들어간다.

{
  "recognized_text": "매일 오후 6시에 고정청정 예약해줘",
  "voice_context": {"last_route": "", "slot_state": {}},
  "capability_registry": "고정청정이 schedulable capability인지 알려주는 정보",
  "skill_registry": "SCH agent와 schedule skill 목록",
  "preclassifier_hints": "schedule scope와 time semantics evidence"
}

그래서 같은 오후 6시라도 active schedule workflow 안에서는 slot 답변이고, 단독 발화에서는 STT_NULL이나 DEF에 가까울 수 있다.

Planner 입력은 단일 텍스트가 아니라 여러 context 묶음이다.

recognized_text
voice_context
history_context
workflow_context
device_context
capability_registry
skill_registry
preclassifier_hints
ranked_top_flows

중요한 원칙은 현재 발화가 1순위라는 점이다. history와 memory는 continuation, slot filling, correction, cancel, replan 판단에 쓰는 보조 증거다.

3.1 Registry와 Catalog가 제공하는 정보

Planner 앞단에는 두 종류의 정적/준정적 지식이 들어온다. 이 정보들은 LLM이 임의로 agent나 skill을 만들지 못하게 하는 경계 역할을 한다.

입력 생성 위치 Planner에 주는 정보 영향
capability_registry gemini/a2a/registry/capability_registry*.json 지원 실행 capability, 상태 조회 capability, 설정 capability, 미지원 category, device Q&A topic, product identity anchor, classification hint ODL/DQR/UNS/SCH 가능성, unsupported 판단, preclassifier anchor 생성
skill_registry gemini/a2a/registry/skill_registry.json agent, skill, purpose, when_to_use, when_not_to_use, execution_mode selected_agent/selected_skill/flow_id 후보 제한
agent_skill_catalog registry.loader.build_skill_catalog_excerpt(...) planner prompt에 들어가는 compact agent/skill 설명 family 결정 이후 owner/skill 정렬
flow_catalog registry.loader.build_flow_catalog_excerpt(...) route_family.agent_id.skill_id 형태의 flow 후보 ranked selector와 main planner의 flow 선택 표면

핵심은 registry가 family를 직접 선택하지 않는다는 점이다. Registry는 아래 질문에 답하는 자료다.

이 발화가 우리 기기에서 실행 가능한가?
현재 상태 조회인가, 설정 변경인가, 제품 지식 질문인가?
명확히 미지원인 실행 요청인가?
선택 가능한 agent/skill/flow id는 무엇인가?

3.2 Catalog 후보가 만들어지는 구조

Catalog 후보는 LLM이 상상해서 만드는 것이 아니라 skill_registry.json에 등록된 agent/skill을 코드가 펼쳐서 만든다. 흐름은 아래처럼 보면 된다.

skill_registry.json
  -> load_skill_registry()
  -> build_flow_catalog_excerpt(...)
  -> flow_catalog.flows[]
  -> _flow_catalog(preclassifier_hints)
  -> allowed_route_families 기준 필터링
  -> ranked_top_flows 또는 candidate_agent_ids/candidate_skill_ids
  -> main planner prompt 입력

3.2.1 원본: skill registry

skill_registry는 “우리 시스템에 존재하는 agent와 skill 목록”이다. 예를 들어 구조는 아래 개념이다.

{
  "agents": [
    {
      "agent_id": "SCH",
      "route_family": "SCH",
      "purpose": "schedule workflow 처리",
      "skills": [
        {
          "skill_id": "schedule_reminder_create",
          "purpose": "스케줄 등록",
          "when_to_use": ["알람/타이머/예약/스케줄 등록"],
          "required_capabilities": ["schedule_register"],
          "execution_mode": "workflow"
        }
      ]
    }
  ]
}

여기서 중요한 점은 skill_registry가 판단 결과가 아니라 “선택 가능한 메뉴판”이라는 점이다. Planner는 이 메뉴판 밖의 agent/skill을 만들면 안 된다.

3.2.2 펼치기: flow catalog 생성

registry.loader.build_flow_catalog_excerpt(...)는 agent와 skill을 하나씩 조합해서 planner가 고르기 쉬운 flow 단위로 펼친다.

Flow id는 아래처럼 만들어진다.

{route_family}.{agent_id}.{skill_id}
예: SCH.SCH.schedule_reminder_create
예: ODL.ODL.device_execution_bridge
예: FRG.FRG.freshness_answer_composition

실제 각 flow에 들어가는 값은 아래다.

flow_id
route_family
agent_id
skill_id
agent_description / agent_purpose
agent_when_to_use / agent_when_not_to_use
skill_description / skill_purpose
skill_when_to_use / skill_when_not_to_use
required_capabilities
execution_mode

즉 flow는 “이 family에서, 이 agent가, 이 skill로, 어떤 조건에서 일을 할 수 있다”는 실행 후보 단위다.

3.2.3 줄이기: 이번 발화에서 가능한 flow만 남김

모든 flow를 planner에게 다 주면 후보가 너무 넓어진다. 그래서 flow_selection_core._flow_catalog(preclassifier_hints)가 먼저 allowed_route_families를 만든 뒤, 그 family에 속한 flow만 남긴다.

예시 발화:

매일 오후 6시에 고정청정 예약해줘

가능한 내부 흐름:

preclassifier_hints:
  has_schedule_workflow_scope = true
  has_schedule_management_anchor = true
  has_schedule_time_semantics = true

_policy_route_families(...):
  allowed_route_families = [SCH]

build_flow_catalog_excerpt(..., allowed_route_families=[SCH]):
  flow_catalog.flows = [SCH.SCH.schedule_reminder_create, SCH.SCH.schedule_alarm_create, ...]

다른 예시:

요즘 볼만한 영화 추천해줘
preclassifier_hints:
  has_public_recommendation_cue = true
  has_frg_scope = true
  has_coherent_conversation_purpose = true

_policy_route_families(...):
  allowed_route_families = [FRG, DEF]

build_flow_catalog_excerpt(..., allowed_route_families=[FRG, DEF]):
  flow_catalog.flows = [FRG.FRG.freshness_answer_composition, DEF.DEF.default_conversation, ...]

3.2.4 정렬하기: route priority와 ranked flow

_flow_catalog(...)는 allowed family가 2개 이상이면 _route_priority_for_hints(...)로 flow 순서를 조정한다. 이 정렬은 최종 선택이 아니라 “planner가 먼저 볼 후보 순서”다.

그 다음 _select_router_flow_context(...)에서 두 경로 중 하나를 탄다.

A2A_INLINE_UNIFIED_ROUTER=true 기본값
  -> _heuristic_ranked_flow_result(...)
  -> inline_flow_catalog 기반 top_flows 생성

A2A_INLINE_UNIFIED_ROUTER=false
  -> select_ranked_flows(...)
  -> 별도 ranked flow selector LLM 호출 가능

따라서 ranked flow는 항상 main planner보다 앞에 있는 후보 정리 단계다. 여기서 나온 top_flows는 main planner의 prior로 들어가지만, 최종 authority는 main planner에 남아 있다.

3.2.5 최종 전달: planner가 받는 후보 묶음

결국 planner prompt에는 아래 정보가 같이 들어간다.

agent_skill_catalog:
  전체 또는 필터된 agent/skill 설명

flow_catalog / ranked_top_flows:
  이번 발화에서 우선 검토할 flow 후보

candidate_agent_ids:
  이번 턴에서 열어둔 agent id 목록

candidate_skill_ids:
  candidate agent들이 사용할 수 있는 skill id 목록

ranked_owner_selection:
  ranked flow 기준 1순위 owner 후보

이 구조의 역할 분담은 아래처럼 정리된다.

단계 하는 일 하지 않는 일
skill_registry 가능한 agent/skill 원본 정의 현재 발화 판단 안 함
build_flow_catalog_excerpt agent-skill을 flow 후보로 펼침 후보 우선순위 판단 안 함
_policy_route_families preclassifier evidence로 열어둘 family 결정 최종 family 확정 안 함
_flow_catalog allowed family의 flow만 남기고 정렬 LLM 의미 판단 안 함
ranked_top_flows main planner prior 제공 planner 결정을 강제하지 않음
main planner 최종 family/owner/step 선택 registry 밖 agent/skill 생성하면 안 됨

그래서 catalog 후보 생성은 “LLM이 agent를 고르는 일”이 아니라, LLM이 고를 수 있는 안전한 선택지를 코드가 먼저 만들어주는 과정이다.

3.3 Capability Registry가 Preclassifier에 주는 축

build_router_excerpt(...)는 capability registry를 planner용 excerpt로 줄인다. v2 registry 기준 핵심 축은 아래다.

supported_capabilities
execution_capabilities
state_query_capabilities
settings_capabilities
unsupported_request_categories
device_qna_topics
product_identity_anchors
classification_hints
router_hints

Preclassifier는 이 excerpt를 사용해 execution_matches, state_query_matches, settings_matches, unsupported_matches, device_qna_topics 매칭을 만든다. 따라서 단순히 문장 표면만 보는 것이 아니라 “우리 제품/기기에서 지원되는 기능 목록”과 비교해 증거를 만든다.

4. 판단 파이프라인

4.1 파이프라인을 쉽게 읽는 방법

17단계로 보면 복잡하지만, 사람에게 설명할 때는 4단계로 줄이면 된다.

큰 단계 실제 내부 단계 한 줄 설명
증거 만들기 1~8 발화, context, registry에서 판단 재료를 만든다.
후보 좁히기 9~11 가능한 agent/skill과 workflow continuation을 추린다.
계획 만들기 12 LLM planner가 family, owner, step을 만든다.
보정/실행 연결 13~17 애매한 경우 재판단하고 runtime이 실행 가능한 형태로 넘긴다.

예시로 드래곤 길들이기는 1차에서 DEF/STT_NULL/FRG가 흔들릴 수 있고, 13~14단계 second pass에서 public title인지 다시 본다.

현재 route_turn(...)의 큰 순서는 아래와 같다.

  1. recognized_textvoice_context 정규화
  2. history context merge
  3. ranked flow context 선택
  4. preclassifier hints 생성
  5. reply object meta 생성
  6. 필요 시 LLM object typer 실행
  7. replyability meta 생성
  8. 필요 시 LLM replyability judge 실행
  9. candidate agent/skill pool 결정
  10. active schedule workflow short-circuit 확인
  11. 특수 shortcut 또는 heuristic-only mode 처리
  12. main planner LLM 호출
  13. second pass 필요 여부 판단
  14. second pass family judge 실행
  15. ranked flow center 보정
  16. replan escape policy 적용
  17. decision metadata, usage metrics 부착

4.1.1 각 챕터를 읽는 공통 포맷

이 문서의 각 레이어는 아래 5개 질문으로 읽으면 된다. 이 포맷을 유지해야 “개념 설명”이 아니라 실제 구현 흐름으로 이해할 수 있다.

질문 확인할 내용 예시
입력은 무엇인가 이전 단계에서 받은 dict/list/string recognized_text, preclassifier_hints, flow_catalog
누가 처리하는가 실제 함수 또는 prompt _policy_route_families(...), route_turn(...), main planner prompt
어떤 기준으로 바꾸는가 alias match, flag 조합, LLM prompt 판단, workflow state capability alias, unsupported category, runner-up pair
출력은 무엇인가 다음 단계로 넘어가는 구조화 값 allowed_route_families, reply_object_meta, steps
다음 단계에 어떤 영향을 주는가 후보 제한, prior 제공, dispatch 결정, replan 판단 candidate pool 제한, second pass 개방, device task 생성

예를 들어 preclassifier 장은 아래처럼 읽어야 한다.

입력: recognized_text + capability_registry_excerpt + voice_context
처리: build_preclassifier_hints(...)
기준: alias/category/surface cue/context state 매칭
출력: has_frg_scope, has_schedule_workflow_scope, unsupported_request_categories ...
영향: allowed_route_families, candidate_agent_ids, reply_object_meta, second_pass_strategy

main planner 장은 아래처럼 읽는다.

입력: 원문 발화 + evidence packet + catalog/ranked_top_flows + workflow_context
처리: Gemini main planner LLM + response_schema
기준: dominant first-response direction, family policy, candidate flow contract
출력: selected_routes, selected_agent, selected_skill, steps, missing_slots
영향: runtime dispatch, multi-turn 유지, task orchestration 시작

이 포맷을 기준으로 보면, 어느 단계가 deterministic code이고 어느 단계가 LLM 판단인지 분리해서 볼 수 있다.

4.2 LLM 없이 앞단 판단이 가능한 이유

여기서 중요한 점은 1~8단계가 최종 family를 고르는 단계가 아니라는 것이다. 이 단계들은 대부분 “판단”이라기보다 증거 생성, 후보 정리, 위험 신호 표시에 가깝다.

즉 LLM이 없어도 아래 작업은 코드로 가능하다.

단계 기본 LLM 여부 어떻게 동작하는가 산출물의 의미
recognized_text 정규화 아니오 문자열 trim, 공백/기호/표면형 정리 같은 발화를 일관된 형태로 본다.
voice_context merge 아니오 이전 turn, active workflow, slot_state를 dict로 병합 지금 발화가 새 요청인지 continuation인지 볼 재료를 만든다.
ranked flow context 보통 아니오, 설정에 따라 가능 기본은 inline catalog/ranking. A2A_INLINE_UNIFIED_ROUTER=false면 ranked flow selector LLM 경로 가능 후보 flow와 owner 후보를 좁힌다.
preclassifier hints 아니오 capability registry, unsupported category, 문장 표면 cue, workflow state를 코드로 매칭 has_frg_scope, has_schedule_workflow_scope 같은 boolean evidence를 만든다.
reply object meta 아니오 preclassifier flag 조합을 if/elif로 object type에 매핑 첫 useful reply의 대상이 device/public/conversation/unknown 중 어디에 가까운지 표시한다.
LLM object typer 기본 아니오 A2A_OBJECT_TYPER_MODE=llm일 때만 LLM으로 object type을 재판단 object meta 보조 judge다. 기본값은 disabled다.
replyability meta 아니오 conversation/STT_NULL 관련 flag를 보고 bounded reply 가능성을 계산 지금 바로 좁은 답변이 가능한지, whole-intent repair가 필요한지 표시한다.
LLM replyability judge 기본 아니오 A2A_REPLYABILITY_JUDGE_MODE=llm일 때만 LLM 호출 replyability 보조 judge다. 기본값은 disabled다.

그래서 이 구간의 출력은 “LLM이 의미를 이해해서 정답을 낸 결과”가 아니다. 더 정확히는 main planner LLM이 과추론하지 않도록 입력을 구조화한 evidence packet이다.

예시로 유튜브 틀어줘가 들어오면 LLM 없이도 아래는 만들 수 있다.

recognized_text: 유튜브 틀어줘
preclassifier_hints.unsupported_request_categories: [streaming_or_app_execution]
preclassifier_hints.has_execution_capability_anchor: false
reply_object_meta.object_type: unsupported_execution_target
replyability_meta.replyability_type: uncertain
candidate route bias: UNS 쪽으로 강함

이 단계가 UNS를 최종 확정하는 것은 아니다. 다만 “명령형이니 ODL”로 잘못 가는 것을 막기 위해, main planner LLM에 지원 불가 실행 요청일 가능성이 높다는 구조화된 증거를 넘긴다. 최종 family 선택은 뒤의 main planner LLM, 필요 시 second pass judge에서 이루어진다.

반대로 매일 오후 6시에 고정청정 예약해줘는 LLM 없이도 아래 증거를 만들 수 있다.

시간/반복 표현: 있음
스케줄 관리 cue: 있음
지원 가능한 schedule workflow 후보: 있음
reply_object_meta.object_type: workflow_target
candidate route bias: SCH 쪽으로 강함

이것도 SCH를 코드가 최종 확정한다는 뜻은 아니다. 스케줄로 볼 충분한 근거가 있다는 형태로 planner에게 전달하는 것이다.

정리하면 현재 구조는 아래처럼 봐야 한다.

앞단 deterministic layer
  = 문자열/상태/registry 기반 evidence 생성
  = 후보 family와 agent pool 제한
  = LLM 입력 품질 개선

main planner LLM
  = evidence를 보고 dominant first-response direction 선택
  = selected_routes, selected_agent, selected_skill, steps 생성

second pass LLM
  = DEF/STT_NULL/FRG 같은 회색지대만 좁게 재판단

따라서 앞단이 LLM 없이 동작할 수 있는 이유는, 그 단계들이 “의미 전체를 창의적으로 해석”하는 게 아니라 이미 정의된 registry, capability, unsupported category, workflow state, 표면 cue를 이용해 판단 재료를 만드는 역할이기 때문이다.

5. Preclassifier 생성 과정

5.0 Preclassifier를 쉽게 말하면

Preclassifier는 “정답을 고르는 모델”이 아니라 “형광펜을 치는 단계”다.

예시 발화:

유튜브 틀어줘

여기서 preclassifier는 아래처럼 표시한다.

실행 말투 있음: true
지원되는 기기 capability anchor: false
unsupported category: streaming_or_app_execution

이 표시 덕분에 planner는 명령형이라는 이유만으로 ODL을 고르지 않고 UNS로 보낼 수 있다.

Preclassifier는 family를 최종 결정하지 않는다. 다만 LLM planner가 과추론하지 않도록 증거와 위험 신호를 구조화한다. 실제 생성 함수는 build_preclassifier_hints(recognized_text, capability_registry_excerpt, voice_context)다.

5.1 입력 정규화

먼저 STT text를 normalize한다.

recognized_text -> lowercase/공백/기호 정리 -> normalized_text
voice_context -> history_hint_strength, slot_state, active_workflow_summary, pending_follow_up_text 읽기
capability_registry_excerpt -> supported/unsupported/device_qna/classification_hints 읽기

이 단계에서 history는 발화를 덮어쓰기 위한 것이 아니라, schedule slot fragment나 follow-up fragment가 실제 continuation인지 보는 보조 증거다.

5.2 Registry 기반 anchor 매칭

Preclassifier는 registry excerpt에서 아래 capability 매치를 만든다.

execution_matches = execution_capabilities alias/function match
state_query_matches = state_query_capabilities alias/function match
settings_matches = settings_capabilities alias/function match
unsupported_matches = unsupported_request_categories alias match
device_qna_topics = device/product Q&A topic match
product_identity_anchors = 제품/assistant identity anchor match

이 매칭에서 파생되는 대표 hint는 아래다.

has_execution_capability_anchor
has_state_query_capability_anchor
has_settings_capability_anchor
has_supported_capability_anchor
unsupported_request_categories
has_stable_device_qna_target
has_product_identity_anchor

켜줘 같은 표면 명령만으로 ODL을 만드는 것이 아니라, registry에 있는 실행/상태/설정 capability와 결합되는지를 본다.

5.3 Domain scope hint 생성

그 다음 발화를 여러 domain scope로 분해한다.

scope 대표 hint 의미
device execution/state has_explicit_odl_intent, has_device_action_cue_surface, has_actionable_device_fragment_surface 현재 기기 실행/상태/설정 가능성
schedule/workflow has_schedule_workflow_scope, has_schedule_management_anchor, has_contextual_schedule_slot_fragment 예약/알람/타이머/스케줄 slot filling 가능성
device Q&A has_dqr_scope, has_howto_or_explanation_cue, has_stable_device_qna_target 실행이 아니라 제품 설명/사용법 가능성
public/external has_frg_scope, has_explicit_public_target, has_public_recommendation_cue, has_general_public_lookup_cue 외부 정보/추천/검색 가능성
conversation has_coherent_conversation_purpose, has_clear_conversation_reply_shape, has_meta_request_cue, has_style_or_format_request_cue DEF 가능한 일반 대화 목적
STT_NULL risk looks_fragmentary, has_no_dominant_interpretation, stt_null_* 발화 붕괴/anchor 없음/복구 위험
unsupported unsupported_request_categories 지원 불가 실행 category

5.3.1 주요 Evidence 한 줄 설명

아래 evidence는 planner에게 “정답”을 주는 값이 아니라, 어느 family가 살아 있는지와 어디가 위험한지를 알려주는 신호다.

Evidence 한 줄 설명 주 영향 영역
normalized_text STT 발화를 비교 가능한 형태로 정리한 텍스트다. 모든 판단
execution_matches registry의 실행 capability alias/function과 발화가 매칭된 결과다. ODL, UNS
state_query_matches 현재 상태 조회 capability와 발화가 매칭된 결과다. ODL, DQR
settings_matches 설정 변경/토글 capability와 발화가 매칭된 결과다. ODL, DQR
has_execution_capability_anchor 실제 지원 실행 기능에 닿는 anchor가 있다는 뜻이다. ODL
has_state_query_capability_anchor 현재값/상태를 물을 수 있는 지원 기능 anchor가 있다는 뜻이다. ODL
has_settings_capability_anchor 설정/모드 변경과 연결되는 지원 기능 anchor가 있다는 뜻이다. ODL, DQR
has_device_action_cue_surface 표면 문장에 실행/제어형 말투가 있다는 뜻이다. ODL, UNS
has_actionable_device_fragment_surface 짧거나 불완전해도 기기 실행 fragment로 복구 가능한 표면이 있다는 뜻이다. ODL, STT_NULL
has_odl_proposal_eligible_surface 바로 실행 대신 “제안/확인형 ODL”로 해석 가능한 device fragment다. ODL
has_schedule_management_anchor 알람/타이머/예약/스케줄 관리 anchor가 있다는 뜻이다. SCH
has_schedule_workflow_scope 발화가 schedule workflow 소유로 이어질 수 있다는 뜻이다. SCH
has_contextual_schedule_slot_fragment active SCH workflow 안에서 다음 slot 답변처럼 보이는 fragment다. SCH continuation
has_dqr_scope 제품/기기 설명, 사용법, 문제해결 쪽으로 답해야 할 scope가 있다는 뜻이다. DQR
has_howto_or_explanation_cue “어떻게/왜/무슨 기능”처럼 설명형 답변을 요구하는 cue다. DQR, DEF
has_stable_device_qna_target device Q&A topic으로 안정적으로 연결되는 대상이 있다는 뜻이다. DQR
has_frg_scope 외부/public 정보 조회나 추천으로 이어질 수 있는 scope가 있다는 뜻이다. FRG
has_explicit_public_target 사람/장소/브랜드/콘텐츠/시장 등 public target이 명시적으로 살아 있다는 뜻이다. FRG
has_public_recommendation_cue 맛집/영화/장소 등 public catalog 추천 의도가 있다는 뜻이다. FRG
has_general_public_lookup_cue 검색/조회/식별/랭킹처럼 외부 지식 조회 방향이 있다는 뜻이다. FRG
has_public_weather_request 날씨 자체 조회처럼 외부/current public 정보가 필요한 요청이다. FRG
has_coherent_conversation_purpose 일반 대화로 자연스럽게 응답할 목적이 살아 있다는 뜻이다. DEF
has_clear_conversation_reply_shape 즉시 한 문장 이상의 bounded reply를 시작할 수 있는 형태다. DEF, STT_NULL
has_meta_request_cue 답변 방식, 도와달라, 말해달라 같은 meta/help 요청 cue다. DEF
has_style_or_format_request_cue 말투/형식/문체 조정 요청이다. DEF
looks_fragmentary 발화가 잘렸거나 독립 의미가 약한 fragment처럼 보인다는 뜻이다. STT_NULL
has_no_dominant_interpretation 여러 해석이 아니라 사실상 우세 해석이 없다는 뜻이다. STT_NULL
stt_null_target_scope STT_NULL이어도 남아 있는 약한 scope가 device/public/workflow/conversation 중 무엇인지 표시한다. STT_NULL subtype
stt_null_recoverability 재질문으로 복구 가능성이 어느 정도인지 표시한다. STT_NULL subtype
stt_null_over_inference_risk 억지 해석으로 잘못 라우팅할 위험이 높은지 표시한다. STT_NULL subtype
unsupported_request_categories 지원 불가 실행 category에 매칭된 항목 목록이다. UNS

읽는 방법은 단순하다. 예를 들어 has_device_action_cue_surface=true만으로는 ODL을 확정하지 않고, has_execution_capability_anchor 또는 has_state_query_capability_anchor 같은 registry 기반 anchor와 같이 봐야 한다. 반대로 has_public_recommendation_cue=true인데 object가 conversation으로 읽히면 DEF와 FRG가 경쟁하므로 2차 판단 후보가 된다.

5.4 Preclassifier는 의미 이해가 아니라 축별 증거 생성이다

우리가 튜닝 중에 “축”이라고 부른 것은 route family 자체가 아니라, 발화를 어느 방향으로 읽을 수 있는지 나타내는 evidence axis다. Preclassifier는 자연어 전체 의미를 연관 추론하지 않는다. 대신 정규화된 발화에서 이미 정의된 alias, category, surface cue, active workflow state가 살아 있는지 확인해 각 축의 신호를 만든다.

확인하는 대표 신호 강해지는 family 약할 때의 처리
실행/상태/설정 축 capability registry alias, “켜줘/꺼줘/확인해줘/설정해줘” ODL 실행 말투만 있으면 ODL 확정 금지
미지원 실행 축 YouTube/음악/영상/전화/외부앱 등 unsupported category UNS 실행 말투가 있어도 supported anchor가 없으면 UNS 후보
스케줄 축 알람/타이머/예약/반복/등록/취소/slot fragment SCH 시간 단어만 있으면 SCH 확정 금지
제품 지식 축 사용법/설명/고장/기능/제품 identity/topic DQR public entity면 DQR 확정 금지
외부 정보 축 사람/장소/브랜드/콘텐츠/날씨/랭킹/추천/검색 FRG public target이 약하면 DEF/STT_NULL과 경쟁
대화 가능 축 감사/거절/감정/의견/말투/도움/짧은 응답 shape DEF 목적이 약하면 STT_NULL과 경쟁
파편/복구 축 잘린 말, completion tail, no dominant interpretation STT_NULL 약한 scope가 있으면 SOFT/GENERIC으로 보류

예를 들어 아래 발화는 단어 포함과 registry 매칭만으로도 축이 만들어진다.

청정 시작해줘
실행/상태/설정 축:
  청정 -> execution capability alias 매칭
  시작해줘 -> action cue 매칭
  has_execution_capability_anchor = true
  has_device_action_cue_surface = true
  ODL 후보 강화

반대로 아래 발화는 실행 말투가 있어도 다른 축이 더 강하다.

유튜브 틀어줘
미지원 실행 축:
  유튜브 -> unsupported_request_categories 매칭
  틀어줘 -> action cue 매칭
  supported execution capability anchor = false
  UNS 후보 강화
  ODL 과추론 방지

또 아래처럼 cue가 약하거나 불완전한 발화는 preclassifier가 억지로 의미를 만들지 않는다.

변화의 시작
높은 장게
드래곤 길들이기
강한 device/schedule/unsupported anchor 없음
public title 또는 conversation 가능성만 약하게 남을 수 있음
reply_object_meta = unknown 또는 conversation
replyability_meta = uncertain
main planner 또는 second pass에서 의미 판단 필요

즉 preclassifier의 역할은 “모든 발화를 이해하는 것”이 아니라, 강한 단서가 있는 축은 올리고 애매한 축은 애매하다고 남기는 것이다.

5.5 축을 이동시키며 튜닝했다는 뜻

성능 튜닝에서 “축을 이동시킨다”는 말은 특정 family를 룰로 강제한다는 뜻이 아니다. 같은 발화가 planner에 들어가기 전에 어떤 evidence axis가 더 강하게 보이도록 할지, 어떤 경우에는 확정하지 말고 2차 판단으로 넘길지 조정한다는 뜻이다.

대표적인 튜닝 방식은 아래와 같다.

튜닝 방식 실제로 바꾼 것 기대 효과 위험
ODL 축 강화 supported capability alias, action/state/settings anchor를 더 안정적으로 살림 명확한 기기 실행/상태조회 누락 감소 UNS/DEF를 ODL로 과해석할 수 있음
ODL 축 제한 action cue만 있고 supported device anchor가 없으면 ODL 확정 금지 “사자기 켜줘” 같은 opaque command 오분류 감소 짧은 기기 fragment 일부가 약해질 수 있음
FRG 축 강화 public recommendation/title/weather/lookup cue를 더 잘 살림 영화/장소/날씨/추천이 DEF/STT_NULL로 떨어지는 문제 감소 일반 대화 추천을 FRG로 과잉 이동 가능
DEF 축 회복 coherent conversation purpose, clear reply shape를 더 인정 자연스러운 짧은 대화가 STT_NULL로 무너지는 문제 감소 broken STT를 DEF로 과구제할 수 있음
STT_NULL 축 제한 target/scope가 살아 있으면 HARD로 닫지 않음 복구 가능한 발화가 바로 실패 처리되는 문제 감소 정말 깨진 STT를 계속 살릴 수 있음
2차 판단 축 개방 DEF/STT_NULL/FRG 경계에서 runner-up과 ambiguity를 보존 1차 과신으로 닫히는 문제 감소 LLM 호출 증가, 일부 flip risk

예시로 요트북 추천해줘를 보면, preclassifier가 정확한 의미를 아는 것은 아니다. 다만 추천해줘는 public recommendation 축과 DEF 대화 축을 동시에 열 수 있다. 튜닝 전에는 DEF 축으로 너무 빨리 닫히면 FRG 복구 기회를 잃었다. 그래서 튜닝 방향은 FRG로 무조건 보내기가 아니라 아래처럼 축을 조정하는 쪽이었다.

기존 나쁜 흐름:
  recommendation surface 있음
  object_type = conversation
  planner confidence = high
  selected = DEF
  second pass 없음

개선 목표:
  recommendation/public 가능성 남김
  runner_up_family = FRG 보존
  confidence = medium/low 가능
  needs_second_pass = true 가능
  second pass에서 DEF vs FRG 좁게 판단

DEFSTT_NULL도 같은 방식이다. 변화의 시작처럼 문장형으로 보이는 발화는 DEF 축을 살릴 수 있지만, target이 없고 reply shape가 약하면 STT_NULL 축도 같이 남긴다. 튜닝은 DEF를 무조건 올리는 게 아니라, “bounded reply target이 살아 있는가”를 기준으로 DEF 축과 STT_NULL 축의 경계를 이동시키는 작업이었다.

따라서 현재 구조에서 축 튜닝은 세 단계로 이루어진다.

1. Preclassifier 축 조정
   어떤 cue/alias/category가 어떤 axis를 켤지 조정

2. Meta 축 조정
   reply_object_meta, replyability_meta가 unknown/conversation/public/device 중 어디로 기울지 조정

3. Planner/Second Pass 축 조정
   1차에서 확정할지, runner-up을 보존하고 2차로 넘길지 조정

이 방식의 장점은 hard rule로 family를 고정하지 않고, LLM planner가 볼 evidence의 방향과 강도만 제어할 수 있다는 점이다. 반대로 단점은 축 하나를 올리면 다른 family가 내려갈 수 있기 때문에, ODL/FRG/DEF/STT_NULL을 항상 함께 benchmark로 확인해야 한다는 점이다.

5.6 Active workflow 관련 hint

voice_context.slot_state.owner_route == SCH 이거나 active_workflow_summarysch로 시작하면 schedule continuation 판단을 위한 hint가 만들어진다.

has_contextual_schedule_slot_fragment = true
조건: active SCH workflow + missing_slots/pending question + 시간/장소/수정 fragment

이 값이 중요한 이유는 내일, 오후 6시, 거실 말고 안방 같은 발화가 단독으로는 DEF/STT_NULL처럼 보여도, active schedule workflow 안에서는 SCH slot filling으로 해석되어야 하기 때문이다.

5.7 Preclassifier 출력의 사용처

Preclassifier hint는 네 군데로 전달된다.

1. flow_selection_core._policy_route_families(...)
   -> allowed_route_families 생성

2. main_router_api._candidate_agent_ids(...)
   -> candidate_agent_ids / candidate_skill_ids fallback 생성

3. build_main_router_contents(...)
   -> main planner prompt payload에 preclassifier_hints 포함

4. reply_object_meta / replyability_meta / second_pass_strategy
   -> DEF/STT_NULL/FRG 회색지대 판단 보조

따라서 preclassifier는 “룰로 최종 route를 고르는 장치”가 아니라, LLM planner와 flow selector의 입력 공간을 안전하게 좁히는 evidence layer다.

6. Candidate Pool과 Ranked Flow

6.0 Candidate Pool 예시

예시 발화:

요즘 볼만한 영화 추천해줘

preclassifier는 public recommendation evidence를 만들고, flow selector는 FRG 관련 flow를 상위 후보로 둔다. 동시에 추천해줘는 일반 대화처럼 보일 수도 있으므로 DEF가 약한 후보로 남을 수 있다.

allowed_route_families = [FRG, DEF]
ranked_top_flows = [FRG public lookup/recommendation, DEF default conversation]

이 구조의 장점은 LLM planner가 모든 agent를 처음부터 뒤지는 게 아니라, 살아 있는 후보 안에서 의미 판단을 한다는 점이다.

Planner는 처음부터 모든 agent를 같은 확률로 열지 않는다. 후보 생성은 아래 순서로 진행된다.

1. preclassifier_hints 생성
2. _policy_route_families(preclassifier_hints)로 열어둘 family 결정
3. build_flow_catalog_excerpt(..., allowed_route_families=...)로 flow 후보 필터링
4. _flow_catalog(...)가 candidate_agent_ids/candidate_skill_ids 추출
5. _select_router_flow_context(...)가 inline 또는 LLM ranked flow를 선택
6. _resolve_candidate_pool(...)이 main planner에 넣을 최종 candidate pool 결정

6.1 Ranked Flow Selector

ranked flow selector는 main planner가 보기 전에 “이번 발화에서 어느 flow를 먼저 볼지” 정하는 단계다. 최종 route를 확정하는 단계가 아니다.

실제 호출 경로는 아래다.

route_turn(...)
  -> build_preclassifier_hints(...)
  -> _planner_top_k_for_context(...)
  -> _select_router_flow_context(...)
      -> _flow_catalog(preclassifier_hints)
          -> _policy_route_families(preclassifier_hints)
          -> build_flow_catalog_excerpt(SKILL_REGISTRY, allowed_route_families=...)
          -> candidate_agent_ids / candidate_skill_ids 추출
      -> _heuristic_ranked_flow_result(...) 또는 select_ranked_flows(...)

현재 기본 설정에서는 A2A_INLINE_UNIFIED_ROUTER=true이므로 보통 _heuristic_ranked_flow_result(...)가 사용된다. 이 경우 별도 LLM 호출 없이 catalog와 preclassifier evidence로 top_flows를 만든다. A2A_INLINE_UNIFIED_ROUTER=false이면 select_ranked_flows(...) 경로로 가며, 이때는 별도 ranked flow selector LLM을 사용할 수 있다.

입력 payload는 개념적으로 아래 구조다.

{
  "utterance": "요즘 볼만한 영화 추천해줘",
  "preclassifier_hints": {
    "has_frg_scope": true,
    "has_public_recommendation_cue": true,
    "has_coherent_conversation_purpose": true
  },
  "allowed_route_families": ["FRG", "DEF"],
  "candidate_agent_ids": ["FRG", "DEF"],
  "candidate_skill_ids": ["freshness_answer_composition", "default_conversation"],
  "flow_catalog": {
    "flows": [
      {"flow_id": "FRG.FRG.freshness_answer_composition", "route_family": "FRG"},
      {"flow_id": "DEF.DEF.default_conversation", "route_family": "DEF"}
    ]
  }
}

출력은 top_flows다.

{
  "top_flows": [
    {
      "flow_id": "FRG.FRG.freshness_answer_composition",
      "route_family": "FRG",
      "agent_id": "FRG",
      "skill_id": "freshness_answer_composition",
      "confidence": "medium",
      "rationale": "public recommendation target remains"
    },
    {
      "flow_id": "DEF.DEF.default_conversation",
      "route_family": "DEF",
      "agent_id": "DEF",
      "skill_id": "default_conversation",
      "confidence": "low",
      "rationale": "recommendation wording can also be conversational"
    }
  ],
  "candidate_agent_ids": ["FRG", "DEF"],
  "candidate_skill_ids": ["freshness_answer_composition", "default_conversation"]
}

여기서 FRG가 1순위라고 해서 최종 FRG가 확정되는 것은 아니다. main planner는 원문 발화, evidence, ranked flow, response schema를 모두 보고 최종 selected_routes를 만든다. 다만 ranked flow는 “FRG를 먼저 검토하라”는 prior 역할을 한다.

6.1.1 후보 생성 단계별 역할

단계 실제 함수 입력 출력 역할
family gate _policy_route_families(...) preclassifier_hints allowed_route_families 이번 턴에 열어둘 family 후보 결정
flow 펼치기 build_flow_catalog_excerpt(...) skill_registry, allowed family flow_catalog.flows[] agent-skill 조합을 flow 후보로 변환
flow 정렬 _flow_catalog(...) preclassifier_hints 정렬된 flow_catalog evidence에 맞는 family 순서 우선
ranked 선택 _heuristic_ranked_flow_result(...) 또는 select_ranked_flows(...) utterance, flow catalog, hints ranked_top_flows main planner prior 생성
candidate pool _resolve_candidate_pool(...) ranked result, hints candidate_agent_ids, candidate_skill_ids planner 입력 후보 확정
최종 선택 main planner LLM 원문, hints, catalog, candidates selected_routes, selected_agent, steps 실제 route/owner/plan 결정

6.1.2 왜 이렇게 나누는가

이 구조를 나누는 이유는 세 가지다.

1. 안전성
   LLM이 registry에 없는 agent/skill을 만들지 못하게 한다.

2. 성능
   모든 flow를 prompt에 넣지 않고 관련 후보만 넣는다.

3. 회색지대 보존
   FRG/DEF, DEF/STT_NULL처럼 애매한 후보를 둘 다 남겨 main planner 또는 second pass가 판단하게 한다.

6.1.3 Catalog / Ranked Flow 필드 한 줄 설명

Catalog 계열 값은 planner가 “없는 agent/skill/flow를 만들지 않게” 하고, family 결정 이후 실제 owner를 정렬하게 하는 계약이다.

항목 한 줄 설명 사용 위치
capability_registry 기기가 지원하는 실행/상태/설정/스케줄/미지원 범위를 담은 기능 사전이다. preclassifier, planner feasibility 판단
supported_capabilities planner에 노출 가능한 전체 지원 capability 요약이다. ODL/DQR/SCH 가능성 판단
execution_capabilities 실제 실행 side effect를 만들 수 있는 capability 목록이다. ODL 후보 생성
state_query_capabilities 현재 상태/센서/환경 값을 조회할 수 있는 capability 목록이다. ODL 상태조회, DQR 경계
settings_capabilities 설정/모드/토글 변경과 관련된 capability 목록이다. ODL 설정 실행, DQR how-to 경계
unsupported_request_categories 음악/영상/외부기기/전화연결 등 지원하지 않는 실행 category 목록이다. UNS 후보 생성
device_qna_topics 제품 설명/사용법/문제해결로 답할 수 있는 device Q&A topic 목록이다. DQR 후보 생성
product_identity_anchors 제품명/assistant명/브랜드성 anchor 목록이다. DQR, ODL 과잉방지
classification_hints preclassifier가 사용하는 cue 묶음이다. schedule/public/conversation/STT_NULL hint 생성
skill_registry route family별 agent와 skill 정의를 담은 실행 주체 사전이다. catalog excerpt 생성
agent_skill_catalog planner prompt에 들어가는 compact agent/skill 설명이다. selected_agent/selected_skill 선택
flow_catalog agent-skill 조합을 flow_id 단위로 펼친 후보 목록이다. ranked selector 입력
flow_id {route_family}.{agent_id}.{skill_id} 형태의 실행 후보 식별자다. selected_flow_id
route_family flow가 속한 최상위 routing family다. selected_routes, owner_selection
agent_id 실제 specialist agent 소유자다. selected_agent, runtime dispatch
skill_id agent 안에서 사용할 세부 skill이다. selected_skill, agent execution
agent_purpose agent가 맡는 업무 범위를 짧게 설명한다. owner 판단
skill_purpose skill이 수행하는 구체 기능을 설명한다. skill 판단
when_to_use 이 agent/skill을 선택해야 하는 조건이다. flow ranking, planner prompt
when_not_to_use 이 agent/skill을 선택하면 안 되는 조건이다. 오분류 방지
required_capabilities 해당 skill 실행에 필요한 capability 조건이다. feasibility / fallback 판단
execution_mode cloud/device/realtime/workflow 등 실행 성격을 나타낸다. steps.execution_target, runtime
allowed_route_families preclassifier evidence로 이번 턴에 열어둔 family 후보 목록이다. flow catalog filtering
ranked_top_flows flow selector가 고른 상위 flow 후보들이다. main planner prior
ranked_primary_flow ranked_top_flows 중 1순위 flow다. 기본 owner prior
ranked_owner_selection ranked flow 기준의 owner 후보 요약이다. owner_selection prior
ranked_owner_selection_hint_strength ranked owner를 얼마나 강하게 믿을지 나타낸다. weak이면 planner 재판단 여지 확대
ranked_owner_alternative_selection top owner가 약할 때 비교할 대안 owner다. ambiguous owner 판단
ranked_candidate_source 후보가 ranked flow에서 왔는지 preclassifier fallback인지 표시한다. decision metadata/debug

Catalog 값은 family를 강제로 결정하는 값이 아니다. 예를 들어 top ranked flow가 ODL이어도 발화가 외부 영화 추천이면 planner는 FRG로 뒤집을 수 있다. 다만 최종 family가 FRG로 정해진 뒤에는 catalog 안의 FRG flow 중 하나를 골라 selected_flow_idselected_skill을 맞추는 것이 정상이다.

6.2 allowed_route_families 생성

flow_selection_core._policy_route_families(preclassifier_hints)는 preclassifier hint를 보고 열어둘 family 후보를 만든다. 예를 들어 public target과 conversation shape가 같이 있으면 FRG, DEF를 같이 남길 수 있다. device action과 unsupported category가 같이 있으면 ODL, UNS 경쟁을 남긴다.

이 값은 두 가지 역할을 한다.

flow_catalog에서 허용 family만 필터링
ranked selector가 볼 후보군을 줄임

6.3 candidate_agent_ids / candidate_skill_ids

Main planner 후보는 아래 순서로 결정된다.

1. ranked_top_flows가 있으면
   _ranked_candidate_agent_ids(...) / _ranked_candidate_skill_ids(...) 사용

2. ranked 후보가 없으면
   _candidate_agent_ids(preclassifier_hints) 사용

3. candidate source 기록
   ranked_top_flows 또는 preclassifier_fallback

_candidate_agent_ids(...)는 대략 아래 mapping을 수행한다.

hint 추가되는 agent 후보
looks_fragmentary STT_NULL, DEF
has_frg_scope FRG
has_schedule_workflow_scope SCH
has_dqr_scope DQR
execution/state/settings anchor ODL
unsupported category UNS
coherent conversation/meta/style/planning DEF

6.4 ranked_owner_selection의 의미

ranked_owner_selection은 “현재 catalog/ranked flow 기준 가장 그럴듯한 owner”다. 하지만 최종 family authority는 main planner에 남아 있다.

Planner prompt에는 아래 값들이 같이 들어간다.

ranked_primary_flow
ranked_top_flows
ranked_allowed_route_families
ranked_candidate_agent_ids
ranked_candidate_skill_ids
ranked_candidate_source
ranked_owner_selection
ranked_owner_selection_hint_strength
ranked_owner_alternative_selection
agent_skill_catalog

정책은 다음과 같다.

강한 ranked owner: 기본 owner prior로 존중
약한 ranked owner: 발화 의미와 object/replyability 증거로 재판단
최종 family가 정해진 뒤: 같은 family 안에서 flow/skill alignment 보정
다른 family로 강제 override: 지양

즉 ranked flow는 planner를 대체하지 않는다. Catalog 기반 후보/owner prior를 제공하고, planner는 발화 의미와 evidence를 합쳐 최종 route/step을 만든다.

7. Reply Object Typer

7.1 Object Type 예시

같은 “추천해줘”라도 첫 useful reply의 대상이 무엇인지에 따라 family가 달라진다.

발화 reply object 자연스러운 family
맛집 추천해줘 outside_option FRG
나한테 어울리는 말투 추천해줘 conversation DEF
청정 모드 추천해줘 supported_device 또는 device_knowledge ODL/DQR
그거 추천해줘 unknown STT_NULL 또는 active workflow 확인

즉 object typer는 “무엇에 대해 답해야 하는가”를 잡아주는 보조 판단이다.

reply_object_meta는 “첫 useful reply가 무엇에 대해 말하는가”를 본다.

예시 object type:

supported_device
unsupported_execution_target
public_entity
outside_option
device_knowledge
workflow_target
conversation
unknown

이 값은 특히 DEF, STT_NULL, FRG, ODL, DQR 경계에서 중요하다.

7.2 Reply Object Meta가 실제로 만들어지는 방식

기본 경로는 LLM이 아니라 _build_reply_object_meta(preclassifier_hints, recognized_text)다. 이 함수는 preclassifier flag 조합을 보고 object type을 정한다.

입력:
  preclassifier_hints
  recognized_text

처리:
  has_schedule -> workflow_target
  unsupported only -> unsupported_execution_target
  public recommendation -> outside_option
  public lookup/entity -> public_entity
  execution/state/settings -> supported_device
  product/how-to/device qna -> device_knowledge
  strong conversation -> conversation
  weak/no signal -> unknown

출력:
  object_type
  object_role
  object_confidence
  reason_signals

예를 들어 요즘 볼만한 영화 추천해줘는 public recommendation cue가 살아 있으면 outside_option 쪽으로 기운다. 반대로 나한테 어울리는 말투 추천해줘는 public target이 없고 style/conversation cue가 강하므로 conversation 쪽으로 기운다.

LLM object typer는 항상 도는 것이 아니다. A2A_OBJECT_TYPER_MODE=llm이고 gate가 열렸을 때만 _llm_reply_object_meta(...)가 기본 meta를 대체할 수 있다. 따라서 기본 구조는 아래처럼 이해해야 한다.

기본: deterministic object meta
조건부: LLM object typer로 보정
최종: main planner가 object meta를 evidence로 사용

8. Replyability Judge

8.1 Replyability 예시

DEFSTT_NULL의 차이는 “대충 답할 수 있나”가 아니라 “지금 바로 좁은 첫 응답을 시작할 수 있나”다.

발화 replyability 판단
고마워 bounded reply 가능 DEF
소개 시켜줘 대상이 약하지만 대화 목적 가능 DEF 또는 2차 판단
높은 장게 whole-intent repair 필요 STT_NULL
그거 말고 내일 active workflow가 있으면 slot 답변 가능 SCH continuation 가능

replyability_meta는 지금 바로 bounded reply를 시작할 수 있는지 본다.

가능한 방향:

bounded_reply_now
whole_intent_repair
uncertain

이 판단은 DEFSTT_NULL 경계에 중요하다. “대충 대답을 만들 수 있다”가 아니라, 첫 응답 대상이 살아 있는지를 본다.

8.2 Replyability Meta가 실제로 만들어지는 방식

기본 경로는 _build_replyability_meta(preclassifier_hints)다. 이 함수는 conversation/STT_NULL 관련 flag를 보고 세 가지 중 하나로 분류한다.

입력:
  preclassifier_hints

처리:
  stt_null_response_strategy가 retry 계열
    -> whole_intent_repair

  coherent conversation / default cue / clear reply shape / supported generic help
    -> bounded_reply_now

  위 조건이 모두 약함
    -> uncertain

출력:
  replyability_type
  replyability_confidence
  replyability_reason

예시:

고마워
  -> clear conversation reply shape
  -> bounded_reply_now

높은 장게
  -> no dominant interpretation / retry strategy
  -> whole_intent_repair

드래곤 길들이기
  -> public title 가능성은 있으나 reply shape가 애매할 수 있음
  -> uncertain 또는 FRG second pass 후보

LLM replyability judge는 항상 도는 것이 아니다. A2A_REPLYABILITY_JUDGE_MODE=llm일 때만 _llm_replyability_meta(...)가 호출될 수 있다. 따라서 기본 구조는 preclassifier 기반 추정 -> 필요 시 LLM 보조 -> main planner evidence다.

9. Main Planner LLM

9.1 Planner Output 예시

예시 발화:

오늘 비 오는지 확인하고, 오면 우산 챙기라고 알려줘

가능한 output은 family 하나가 아니라 step plan이다.

{
  "turn_mode": "multi_step",
  "selected_routes": ["FRG", "SCH"],
  "steps": [
    {"id": "step_1", "route": "FRG", "purpose": "오늘 비 예보 확인", "output_key": "rain_forecast"},
    {"id": "step_2", "route": "SCH", "purpose": "비가 오면 우산 알림 생성", "depends_on": ["step_1"], "input_from": {"forecast": "rain_forecast.result"}}
  ]
}

이 예시는 planner가 “검색 후 그 결과를 schedule에 넘긴다”는 의존성을 표현해야 함을 보여준다.

Main planner는 아래 출력 계약을 만든다.

turn_mode
selected_routes
selected_flow_id
selected_agent
selected_skill
owner_selection
steps
missing_slots
family_confidence
runner_up_family
needs_second_pass
ambiguity_reason
stt_null_subtype
stt_null_target_scope
stt_null_recoverability
ask_follow_up
tts_response

steps는 multi-step과 device task 연결의 핵심이다.

주요 step 필드:

id
route
purpose
depends_on
input_from
output_key
execution_target
wait_policy
token_text
device_task

9.2 Main Planner가 실제로 수행하는 역할

Main planner는 앞단 evidence를 그대로 받아서 최종 실행 계획으로 바꾸는 첫 번째 LLM 판단 지점이다. 입력과 출력의 관계는 아래처럼 보면 된다.

입력 packet:
  recognized_text
  voice_context / workflow_context
  preclassifier_hints
  reply_object_meta
  replyability_meta
  ranked_top_flows
  ranked_owner_selection
  candidate_agent_ids / candidate_skill_ids
  agent_skill_catalog
  capability_registry excerpt

LLM 판단:
  dominant first-response direction은 무엇인가
  singleton / multi_turn / multi_step / parallel 중 무엇인가
  최종 family는 무엇인가
  어느 agent/skill이 owner인가
  필요한 step은 몇 개인가
  step 간 의존성이 있는가
  slot이 부족한가
  second pass가 필요할 만큼 애매한가

출력:
  selected_routes
  selected_flow_id
  selected_agent / selected_skill
  owner_selection
  steps[]
  missing_slots
  family_confidence / runner_up_family / needs_second_pass

중요한 점은 main planner가 앞단 evidence를 “무조건 따르는” 것이 아니라는 점이다. 예를 들어 ranked flow 1순위가 DEF여도, 발화와 public target이 강하면 FRG로 뒤집을 수 있다. 반대로 preclassifier가 public recommendation cue를 약하게 켰더라도 실제 발화가 추천해줘 단독처럼 catalog target이 없으면 DEF 또는 STT_NULL로 갈 수 있다.

하지만 planner가 마음대로 할 수 없는 경계도 있다.

registry에 없는 agent_id를 만들면 안 됨
skill_registry에 없는 skill_id를 만들면 안 됨
selected_routes[0], selected_agent, selected_skill, selected_flow_id는 서로 정렬되어야 함
steps[].route는 selected route/family와 충돌하면 안 됨
side effect가 있으면 slot/confirmation/wait_policy를 명확히 해야 함

즉 main planner는 “최종 의미 판단자”이지만, catalog와 schema 안에서만 계획을 만들어야 한다.

9.3 Planner Output이 다음 단계에 주는 영향

Planner output은 바로 사용자 응답이 아니라 runtime의 실행 지시서다. 다음 단계 영향은 아래와 같다.

Planner output Runtime 영향 예시
selected_routes 어떤 family agent로 dispatch할지 결정 ODL, FRG, SCH
selected_agent 실제 sub-agent owner 결정 ODL agent 호출
selected_skill agent 내부 skill 선택 schedule_reminder_create
turn_mode singleton/multi-turn/multi-step 처리 방식 결정 slot 부족이면 multi_turn
steps 순차/병렬/의존 실행 계획 생성 step_2 depends_on step_1
missing_slots 사용자에게 후속 질문 유지 “몇 시에 실행할까요?”
family_confidence second pass 또는 fallback 판단 보조 medium/low면 회색지대 가능
runner_up_family 2차 pairwise judge 후보 DEF vs FRG
needs_second_pass second pass 호출 여부 true면 pairwise judge

10. Second Pass Judge

10.1 Second Pass 예시

예시 발화:

드래곤 길들이기

1차 planner는 이걸 영화 제목으로 보고 FRG로 볼 수도 있고, 불완전한 발화로 보고 STT_NULL로 볼 수도 있다. 이때 second pass는 전체 plan을 다시 짜는 게 아니라 아래 질문만 좁게 다시 묻는다.

이 발화에 stable public/outside target이 살아 있는가?
첫 응답이 lookup/identify/explain 방향인가?

그렇다면 FRG, 아니면 bounded reply 가능성에 따라 DEF/STT_NULL로 간다.

2차 judge는 full replanning이 아니다. 좁은 pairwise family judge다.

현재 핵심 대상:

DEF <-> STT_NULL
DEF <-> FRG
STT_NULL <-> FRG

열리는 대표 조건:

2차 판단 기준:

11. Active Schedule Workflow Short-Circuit

11.1 Schedule Continuation 예시

발화 1:

고정청정 예약해줘

Cloud가 묻는다.

언제 실행할까요?

발화 2:

매일 오후 6시

발화 2만 보면 단순 시간 fragment라 애매하지만, slot_state.owner_route=SCH가 남아 있으므로 planner는 새 route를 고르기보다 SCH continuation으로 본다.

스케줄 workflow가 active이면 새 route 판단보다 continuation이 우선될 수 있다.

조건:

voice_context.slot_state.owner_route == SCH
missing_slots 존재
또는 active_workflow_summary가 sch로 시작

취소 발화가 아니고, 강한 비스케줄 anchor가 없으면 planner는 SCH multi-turn으로 short-circuit한다.

이 구조는 slot filling에서 중요하다. 예를 들어 사용자가 “매일 오후 6시”라고만 말해도, active schedule workflow가 있으면 일반 DEF/STT_NULL이 아니라 SCH continuation으로 처리해야 한다.

12. Multi-step Plan과 Task Orchestration

12.1 Multi-step 실행 예시

예시 발화:

거실로 가서 청정하고 안방으로 가

planner는 아래처럼 물리적 순서를 만든다.

step_1: ODL device, 거실 이동, wait_policy=completed
step_2: ODL device, 거실 청정, depends_on=step_1
step_3: ODL device, 안방 이동, depends_on=step_2

여기서 Cloud LLM은 RUNNING, PROGRESS, COMPLETED마다 다시 생각하지 않는다. DeviceAgent가 순서대로 처리하고, 막힘/실패/사용자 결정 필요 이벤트만 Cloud replan 대상으로 올라온다.

Planner가 multi-step을 만들면 runtime은 orchestrator.py에서 route별 sub-agent를 호출한다.

그 다음 task_manager.py가 아래 조건을 보고 device task plan 여부를 판단한다.

turn_mode == multi_step
steps 길이 > 1
step에 depends_on / input_from / output_key 존재
step에 device_task 또는 token_text 존재

Device step은 device_task_requests로 내려간다.

중요한 필드:

execution_target = cloud | device
wait_policy = submitted | completed | event
depends_on = 실행 순서
input_from = 이전 step 결과 사용
token_text = 온디바이스가 소비할 안전한 device intent token

12.2 Planner Step이 Device Task Request로 바뀌는 방식

Multi-step에서 중요한 경계는 planner가 device를 직접 실행하지 않는다는 점이다. Planner는 순서와 의도를 만들고, ODL/Runtime 계층이 device task request로 바꾼다.

흐름은 아래와 같다.

main planner output
  steps[]
    -> runtime.orchestrator가 route별 agent 실행
    -> ODL agent/bridge가 device_task 또는 token_text 생성
    -> runtime.task_manager / workflow_state가 device_task_requests 생성
    -> on-device bridge가 DeviceAgent TaskManager에 전달
    -> DeviceAgent가 QUEUED/RUNNING/PROGRESS/COMPLETED/FAILED 이벤트 반환

예시 planner step:

{
  "id": "step_1",
  "route": "ODL",
  "purpose": "거실로 이동",
  "execution_target": "device",
  "wait_policy": "completed",
  "output_key": "move_result"
}

ODL/bridge 이후 device task request 예시는 아래처럼 바뀐다.

{
  "taskMethod": "setMoveTo",
  "params": {"position": "거실"},
  "cloud_workflow_id": "wf_xxx",
  "cloud_step_id": "step_1",
  "executionMode": "queued_wait",
  "contract_version": "a2a-task-orchestration-v1"
}

steps[]는 Cloud 내부 계획이고, device_task_requests[]는 온디바이스/DeviceAgent가 소비할 실행 계약이다. 둘을 분리해야 planner가 임의의 device API를 직접 호출하지 않고, ODL/DeviceAgent 안전조건을 거쳐 실행할 수 있다.

12.3 Task Event가 Cloud에 주는 영향

DeviceAgent TaskManager는 task 상태를 이벤트로 올릴 수 있다. 하지만 모든 이벤트가 Cloud LLM 재판단을 의미하지는 않는다.

이벤트 Cloud 기본 동작 LLM replan 필요성
QUEUED workflow 상태만 갱신 없음
RUNNING 진행 상태만 갱신 없음
PROGRESS progress/notification 갱신 없음
COMPLETED 해당 step 완료 처리, 다음 step resume 가능 보통 없음
WORKFLOW_COMPLETED workflow 종료 없음
FAILED reason_code/recoverability 확인 필요할 수 있음
BLOCKED 차단 원인 확인 필요할 수 있음
NEEDS_USER_INPUT 사용자 follow-up 필요 필요할 수 있음
requires_cloud_replan=true replan decision 생성 있음

정상 이벤트는 Cloud가 “알고만 있는” 상태다. Cloud LLM이 다시 생각해야 하는 경우는 실패, 차단, 사용자 결정 필요, 또는 requires_cloud_replan이 명시된 경우다.

12.4 Cloud-only Step과 Device Step의 차이

Multi-step이라고 해서 항상 device task가 생기는 것은 아니다.

Cloud-only step:
  FRG 검색, DQR 문서 조회, DEF 요약처럼 Cloud agent가 완료하는 step
  -> device_task_requests 없음
  -> step output이 다음 cloud step input_from으로 전달

Device step:
  ODL 실행, 이동, 청정, 설정 변경처럼 실제 기기 동작이 필요한 step
  -> device_task_requests 생성
  -> DeviceAgent TaskManager 이벤트로 진행 상태 관리

Mixed step:
  Cloud가 먼저 검색/판단하고, 결과를 바탕으로 device step 실행
  -> depends_on/input_from으로 순서와 데이터 의존성 표현

이 차이를 문서와 schema에서 명확히 유지해야, “복합명령 = 무조건 기기 task”로 오해하지 않는다.

13. Planner와 Agent의 경계

13.1 경계 예시

Planner가 할 일과 Agent가 할 일을 섞으면 위험하다.

Planner: "ODL step이 필요하고 device에서 실행해야 한다"까지 결정
ODL Agent/Bridge: 실제 device task나 안전한 token을 구성
DeviceAgent: 현재 상태 조건을 보고 실행/실패/차단 event 반환

즉 Planner가 임의로 <sk_4> 같은 token을 만들어내면 안 된다. token은 catalog/contract 또는 ODL/device bridge가 안전하게 아는 범위에서만 생성되어야 한다.

Planner가 해야 하는 일:

Agent가 해야 하는 일:

Planner가 직접 device API를 호출하거나, 임의로 task token을 생성하면 안 된다. token/task는 catalog/contract 또는 specialist agent 결과를 통해 안전하게 만들어져야 한다.

14. 예시: Cloud-only Multi-step

발화:

보통 교통이 안 좋은 곳을 알아보고 오늘 그곳 정보를 알려줘

가능한 plan:

{
  "turn_mode": "multi_step",
  "steps": [
    {
      "id": "step_1",
      "route": "FRG",
      "purpose": "보통 교통체증이 심한 지역 후보를 찾는다",
      "output_key": "congested_places",
      "execution_target": "cloud",
      "wait_policy": "completed"
    },
    {
      "id": "step_2",
      "route": "FRG",
      "purpose": "step_1 결과 지역의 오늘 정보를 찾는다",
      "depends_on": ["step_1"],
      "input_from": ["congested_places"],
      "output_key": "today_place_info",
      "execution_target": "cloud",
      "wait_policy": "completed"
    },
    {
      "id": "step_3",
      "route": "DEF",
      "purpose": "결과를 사용자에게 자연스럽게 요약한다",
      "depends_on": ["step_2"],
      "input_from": ["today_place_info"],
      "execution_target": "cloud",
      "wait_policy": "completed"
    }
  ]
}

15. 예시: Mixed Cloud + Device Step

발화:

거실로 가서 공기 상태 확인하고 필요하면 청정해줘

가능한 plan:

{
  "turn_mode": "multi_step",
  "steps": [
    {
      "id": "step_1",
      "route": "ODL",
      "purpose": "거실로 이동",
      "execution_target": "device",
      "wait_policy": "completed",
      "token_text": "<sk_move>(position=거실)<sk_end>",
      "output_key": "move_result"
    },
    {
      "id": "step_2",
      "route": "ODL",
      "purpose": "현재 공기 상태 조회",
      "depends_on": ["step_1"],
      "execution_target": "device",
      "wait_policy": "completed",
      "output_key": "air_state"
    },
    {
      "id": "step_3",
      "route": "ODL",
      "purpose": "공기 상태가 나쁘면 청정 시작",
      "depends_on": ["step_2"],
      "input_from": ["air_state"],
      "execution_target": "device",
      "wait_policy": "event"
    }
  ]
}

여기서 Cloud가 매 step마다 LLM을 다시 돌릴 필요는 없다. 정상 진행은 온디바이스/DeviceAgent TaskManager가 처리하고, 실패/차단/사용자 결정 필요 이벤트만 Cloud replan 대상으로 올린다.

16. 예시 흐름 모음

16.1 스케줄 slot filling continuation

발화 1:

고정청정 예약해줘

흐름:

preclassifier: has_schedule_workflow_scope=true
ranked flow: SCH 후보 상위
main planner: SCH / multi_turn / missing_slots 존재
schedule agent: schedule_function=fixed_cleaning, operation=create만 확정
runtime: 질문형 response 반환
session_state.slot_state.owner_route=SCH

사용자에게 나가는 응답:

언제 실행할까요? 반복이면 요일도 같이 말씀해 주세요.

발화 2:

매일 오후 6시부터 7시까지

흐름:

voice_context.slot_state.owner_route=SCH
missing_slots 존재
_schedule_workflow_short_circuit: SCH continuation 유지
main planner 재판단보다 active schedule workflow 우선
schedule agent: repeat_days/start_time/end_time 채움
slot 완료 시 schedule payload 생성

핵심은 두 번째 발화가 단독으로는 DEF 또는 STT_NULL처럼 보일 수 있어도, active SCH workflow가 있으면 SCH continuation으로 처리한다는 점이다.

16.2 Cloud-only multi-step

발화:

보통 교통이 안 좋은 곳을 알아보고 오늘 그곳 정보를 알려줘

흐름:

preclassifier: public/external lookup 신호
main planner: multi_step
step_1 FRG: 보통 교통체증이 심한 지역 후보 검색
step_2 FRG: step_1 결과 지역의 오늘 정보 검색
step_3 DEF: 사용자에게 자연스럽게 요약
runtime: 모두 cloud execution_target, wait_policy=completed

중요한 필드:

{
  "depends_on": ["step_1"],
  "input_from": ["congested_places"],
  "execution_target": "cloud",
  "wait_policy": "completed"
}

depends_on은 순서이고, input_from은 앞 step 결과를 의미적으로 사용한다는 뜻이다.

16.3 Mixed cloud/device plan

발화:

거실로 가서 공기 상태 확인하고 필요하면 청정해줘

흐름:

preclassifier: device action/state anchor
main planner: multi_step
step_1 ODL device: 거실 이동
step_2 ODL device: 공기 상태 조회
step_3 ODL device 또는 cloud-conditioned device: 상태가 나쁘면 청정 시작
runtime.task_manager: device_task_requests 생성
on-device bridge: device_intent_step 순차 실행
DeviceAgent TaskManager: 정상 이벤트는 로컬 진행, 실패/차단만 Cloud replan

중요한 점:

정상 QUEUED/RUNNING/PROGRESS/COMPLETED마다 Cloud LLM을 다시 돌리지 않는다.
FAILED/BLOCKED/PAUSED/requires_cloud_decision만 replan 후보가 된다.

16.4 DEF/STT_NULL/FRG 회색지대 2차 판단

발화:

드래곤 길들이기

가능한 1차 흔들림:

FRG: 영화/콘텐츠 title lookup
DEF: 사용자가 무슨 말을 원하는지 대화형 반응
STT_NULL: 단편 title shard로 보고 repair

2차 judge가 열릴 수 있는 조건:

object_type=unknown 또는 conversation
public/title/lookup 신호가 남아 있음
planner confidence=medium/low 또는 needs_second_pass=true

2차 판단 질문:

stable public/outside target이 살아 있는가?
첫 useful reply가 lookup/identify/explain 방향인가?

그렇다면 FRG, 아니면 bounded conversational reply가 가능하면 DEF, 그것도 아니면 STT_NULL이다.

16.5 STT_NULL subtype 처리

발화:

높은 장게

가능한 흐름:

preclassifier: fragmentary/no dominant interpretation
reply_object_meta: unknown
replyability_meta: whole_intent_repair
main planner: STT_NULL
stt_null_subtype: STT_NULL_HARD 또는 GENERIC/SOFT 중 판단
runtime: subtype에 따라 재질문 문구 선택

subtype 해석:

STT_NULL_HARD: 거의 복구 불가, 다시 말해 달라고 요청
STT_NULL_GENERIC: 일반 clarification 가능
STT_NULL_SOFT: device/public/workflow/conversation 약한 scope가 있어 scoped probe 가능

16.6 Preclassifier와 Catalog 후보 생성 예시

발화:

요트북 추천해줘

가능한 preclassifier 흐름:

normalized_text = "요트북 추천해줘"
execution_matches = []
state_query_matches = []
settings_matches = []
unsupported_matches = []
has_public_recommendation_cue = true
has_frg_scope = true 또는 weak
has_coherent_conversation_purpose = true
has_clear_conversation_reply_shape = 약함
looks_fragmentary = false 또는 weak risk

이후 candidate 흐름:

_policy_route_families -> [FRG, DEF] 또는 [DEF, FRG, STT_NULL]
flow_catalog -> FRG/DEF 관련 flow만 excerpt
ranked_top_flows -> public recommendation flow와 default conversational flow 경쟁
main planner -> selected_routes와 runner_up_family 판단
필요 시 second pass -> DEF ↔ FRG

이 예시에서 catalog가 중요한 이유는 추천해줘만 보고 DEF로 닫지 않고, public catalog recommendation 가능성을 flow 후보로 살려두기 때문이다.

16.7 Capability Registry 기반 ODL/UNS 예시

발화:

유튜브 틀어줘

가능한 preclassifier 흐름:

execution_matches = []
state_query_matches = []
settings_matches = []
unsupported_matches = ["streaming_or_app_execution"]
has_supported_capability_anchor = false
has_explicit_odl_intent = execution-like surface only

후보 흐름:

_policy_route_families -> [UNS, DEF] 중심
_candidate_agent_ids -> UNS 추가
main planner -> UNS

반대로 발화가 청정 시작해줘이고 capability registry에 청정 실행 alias가 있으면 아래처럼 달라진다.

execution_matches = [{function: ..., aliases: [...]}]
has_execution_capability_anchor = true
has_device_action_cue_surface = true
_candidate_agent_ids -> ODL 추가
main planner -> ODL 또는 SCH/ODL context에 따라 결정

즉 ODL/UNS 경계는 “명령형인가”가 아니라 registry 기준 지원 capability anchor가 살아 있는지가 중요하다.

17. 현재 wiki에서 보강된 점

이 페이지는 기존 planner-routing 문서 대비 아래 내용을 추가한다.

18. 남은 정리 과제

남은 과제는 A2A Planner Cleanup Backlog로 분리했다. 이 backlog는 아래 항목을 우선순위, 확인 소스, 완료 기준까지 포함해 추적한다.

권장 착수 순서는 Schema/Prompt Drift -> ODL Token Safety -> Capability Registry Contract -> Skill/Flow Catalog -> Schedule Slot Contract -> Dynamic Replanning Contract -> Second Pass Benchmark -> Docs Dedup이다.

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