# Main Agent 到 Skill 的内部输入契约 ## 用途 定义 Main Agent 交给 `booking-desk-event` 的素材包边界、粗候选结构、调用门槛和内部回传。这个接口只存在于 Main Agent 与 Skill 之间;`candidate_events` 和 `internal_route` 都不是最终对外输出。 Main Agent 负责入口校验、取得当前素材、OCR/表格原始抽取、current/history 分离、粗候选和未覆盖当前意图。Skill 对最终 event type、subtype、目标拆分、事件合并以及 linked/derived events 拥有唯一裁决权。 本接口完全属于内部处理。Main Agent 的素材包、`candidate_events`、Skill 业务根返回值以及任何 `internal_route` 在成为公开 `final_result` 前,都不得调用 `th-hotel-simple-superagent_th_hotel_submit_task_res`。只有最外层 finalizer 在完成公开结果的形成、校验和冻结后调用一次。 ## 目录 - Step 0:Source Message Identity - 事件集合 - Candidate Event Item - 调用门槛 - Skill 最终裁决与内部回传 - 行级与目标级边界 ## Step 0:Source Message Identity 在读取正文、附件、OCR、历史或系统上下文前,先校验 `source_message.source_message_id`。缺失、`null`、空字符串或只包含空白时,不得建立素材包或调用 Skill,固定返回输出契约定义的: ```json { "result_type": "infrastructure_input_error", "error_code": "missing_source_message_id", "retryable": true, "missing_fields": ["source_message.source_message_id"] } ``` 该结果不是 S10、S99、业务事件或人工复核,不展示源邮件通知,不创建 TaskCard。不得猜测、生成、修剪后替换或从 confirmation/group/reservation number 推导 `source_message_id`。 ## 事件集合 事件集合的唯一权威定义在 `00-output-contract.md`: - `active_emittable_event_types`:当前可以由新邮件生成的普通业务 event type。 - `legacy_accepted_event_types`:只用于读取历史兼容数据;不得进入 `candidate_events`,不得由当前邮件新生成。 - `business_review_outcomes`:业务复核 outcome;不得进入 `candidate_events`,也不能独立满足 Skill 调用门槛。 `possible_event_types` 只能使用 `active_emittable_event_types` 中的精确值。S10、S99、`Need Manual Review`、`Note`、`Allotment Maintenance` 或其他 legacy/review/入口值都不是合法粗候选类型。 ## Candidate Event Item 每个相互独立、由 current evidence 支持的业务信号形成一个粗候选: ```json { "candidate_id": "C1", "classification_status": "coarse_supported_signal", "possible_event_types": ["Voucher Received", "Payment Evidence"], "evidence_refs": [ { "source": "current_attachments", "index": 0 } ], "target_hints": [ { "case_keys": { "group_code": null, "confirmation_number": null, "reservation_number": null, "block_code": null }, "target_locator_raw": null } ], "uncertainty_codes": ["document_subtype_unresolved"] } ``` 固定规则: - `candidate_id` 必须匹配 `^C[1-9][0-9]*$`,并在本素材包中唯一。 - `classification_status` 固定为 `coarse_supported_signal`,表示 Main Agent 没有作最终业务裁决。 - `possible_event_types` 必须非空、去重,只能使用 active 集合精确值;可以保留多个候选类型。 - `evidence_refs` 必须非空,只能引用 current 素材。`source` 只能是 `body_current`、`current_attachments`、`current_tables`、`current_ocr` 或 `parent_child_split_evidence`;`index` 必须是存在于对应素材中的非负整数索引,`body_current` 固定使用 `0`。 - `target_hints` 是非权威提示,可以为空或包含多个 item。每个 item 固定包含四键 `case_keys` 和原始 locator;不唯一时保持 `null`,不得猜测。 - Allotment / Control Block / Parent Group 的 target hint 使用同一对象语义:只识别到 `group_code` 或 `block_code` 任一边时,归一后同时填入两边;两边同时出现但冲突时均保持 `null`,并在 `target_locator_raw` 和 split evidence 中保留两个原始候选。Child/普通 Group Block 不做双 key 补齐。 - `uncertainty_codes` 可以为空;有不确定性时使用稳定 reason code,不用自然语言结论替代。 - history-only 内容不得创建粗候选。历史只能在 current 粗候选已经存在后绑定目标、旧值或上下文。 - 每个相互独立的 current 信号都必须有自己的粗候选;同一行或同一目标中的主业务信号和补充 Trace 信号也分别建 candidate。Skill 再按最终目标和业务规则合并、拆分或建立 linked relationship。 - 一个粗候选不等于一个最终事件。Skill 可以拆成多个目标事件、合并同目标信号,或生成契约明确要求的 linked/derived events。 - 最终事件在 `context_used.main_candidate_ids` 中记录所使用的 `candidate_id`;一个事件可关联多个 candidate,一个 candidate 也可关联多个最终事件。最终输出只允许保留这些 ID 作为 provenance,不得复制 `candidate_events` item、`possible_event_types`、`uncertainty_codes` 或任何 `internal_route`。 ## 调用门槛 完成全部 current 内容盘点后: 1. 输入可理解,且没有任何 active 业务信号:Main Agent 输出 S10,不调用 Skill。 2. 输入不足,无法判断是否存在任何 active 业务信号:Main Agent 输出 S99,不调用 Skill。 3. `candidate_events` 至少有一个满足本契约的粗候选:Main Agent 调用 Skill,并同时传递 `unknowns`。 仅有 unsupported/legacy 内容、`unknowns` 或 review outcome 不能满足调用门槛。正文只有泛化 `see attached` 且附件无法取得、方向不可识别时走 S99;动作方向已明确为 active 事件但必要附件不可读时,仍形成粗候选,由 Skill 生成业务复核结果。 ## Skill 最终裁决 - Skill 必须重新验证 current evidence,不得把 `possible_event_types` 当作最终结论。 - event type 和 subtype 已知但字段、目标、映射或证据不安全时,保留该 active `event_type`,并附非空 `manual_review`;`manual_review.missing_fields[]` 使用 RFC 6901 JSON Pointer。 - 只有 active 业务方向已确认,但最终 event type 或 subtype 仍无法确定时,才使用 `business_review_outcomes` 中的 `Need Manual Review`。 - 业务类型已知的 manual-review event 和 type-unknown `Need Manual Review` 都是业务输出,不得改成 S99。 如果 Skill 验证后确认所有粗候选都没有 active 事件,返回以下内部结果: ```json { "internal_route": "no_supported_event", "reason_code": "skill_validation_found_no_active_event", "candidate_ids": ["C1"] } ``` Main Agent 必须把该内部结果转换成正常 S10;不得把 `internal_route` 直接对外返回,也不得把它转换成 S99 或业务人工复核。 `no_supported_event` 本身不得调用或进入 mandatory submit。转换完成后的 S10 才是公开 `final_result`,由 Main Agent 提交一次并原样返回。 `no_supported_event.candidate_ids` 必须非空,列出本次已完成验证且均被否定的全部粗候选 ID。 如果素材包或 candidate item 违反本契约,Skill 返回内部 contract error: ```json { "internal_route": "candidate_package_contract_error", "reason_code": "invalid_candidate_package", "candidate_ids": ["C1"], "contract_errors": [ { "path": "/candidate_events/0/possible_event_types/0", "code": "not_active_emittable_event_type" } ] } ``` `contract_errors[].path` 使用 RFC 6901 JSON Pointer。该内部错误报告给编排层并停止本次业务处理;不得直接对外,不得映射为 S10、S99 或 `Need Manual Review`。 `candidate_package_contract_error` 本身不得调用或进入 mandatory submit。若编排层基于它形成正式公开错误对象,只提交该公开对象,不提交内部 disposition。 `candidate_package_contract_error.contract_errors` 必须非空。`candidate_ids` 保留仍可安全读取的候选 ID;如果连 ID 都不合法,使用空数组。 ## 行级与目标级边界 QBD/LianTai 当前有效行必须独立处理,不能把不同 current rows 合并成一个主事件。这里的“一行”是隔离边界,不是绝对事件数量: - 普通情况下,一个 current row × 一个目标产生一个主事件。 - 同一行含多个目标时由 Skill 按目标拆分。 - Parent-to-child split 的 linked parent `Cancel Allotment`、linked Trace、TA Recorder 和其他契约明确的 derived event 可以在同一行主事件之外额外产生。 - `parent_child_split_evidence` 必须保留 parent code 的来源、完整释放/不保留余量证据、child code 有序列表、显式 cancel(如有)及其目标、parent original room summary 和关系来源;Main Agent 不自行去重或决定最终事件。 - Main Agent 只保留行、highlight、目标提示和不确定点;最终拆分由 Skill 完成。