# Divination Run Protocol (Frontend <-> Backend) This document defines the structured contract for divination run input, backend hexagram derivation, and run event output. Protocol verification status: - Backend route source: `backend/src/v1/agent/router.py` - Backend derivation source: `backend/src/core/divination/derivation.py` - Runtime payload schema source: `backend/src/schemas/domain/divination.py` ## Compatibility strategy - Run/events contract: `backward-compatible` additive evolution. - History contract (`GET /agent/history`) currently `requires-migration` (see migration notes in this document). - Existing required fields cannot be removed or renamed without migration notes. - Canonical divination terminology values must remain Chinese. ## Route overview - Submit run: `POST /api/v1/agent/runs` - Cancel run: `POST /api/v1/agent/runs/{threadId}/cancel?runId=...` - Stream events: `GET /api/v1/agent/runs/{threadId}/events?runId=...` - History snapshot: `GET /api/v1/agent/history` - Delete session: `DELETE /api/v1/agent/sessions/{threadId}` - Upload attachment: `POST /api/v1/agent/attachments` - Get attachment signed URL: `GET /api/v1/agent/attachments/signed-url?bucket=...&path=...` - Audio transcribe: `POST /api/v1/agent/transcribe` ## Run request contract `RunAgentInput` uses AG-UI shape. This protocol constrains two sections: 1) `messages[0].content` (question text) 2) `forwardedProps.divinationPayload` (structured divination input) ### Required request shape ```json { "threadId": "uuid", "runId": "run_20260403_xxx", "state": {}, "messages": [ { "id": "msg_run_20260403_xxx_user_0", "role": "user", "content": "我最近换工作是否合适?" } ], "tools": [], "context": [], "forwardedProps": { "runtime_mode": "chat", "client_time": { "device_timezone": "Asia/Shanghai", "client_now_iso": "2026-04-03T20:30:00+08:00", "client_epoch_ms": 1775219400000 }, "divinationPayload": { "divinationMethod": "手动起卦", "questionType": "事业", "question": "我最近换工作是否合适?", "divinationTimeIso": "2026-04-03T20:30:00+08:00", "yaoLines": [ "少阳", "少阴", "老阳", "少阴", "少阳", "老阴" ] } } } ``` ### AG-UI required base fields - `state`: required object, frontend sends `{}` by default. - `messages[0].id`: required string id for user message. - `tools`: required array, frontend sends empty array when no tools requested. - `context`: required array, frontend sends empty array when no extra context. ### `divinationPayload` strict rules - `divinationMethod`: enum, allowed values `手动起卦 | 自动起卦` - `questionType`: non-empty string, max 32 chars, recommended Chinese category labels - `question`: non-empty string, max 300 chars - `divinationTimeIso`: RFC3339 datetime with timezone offset - `yaoLines`: exactly 6 items, order is `初爻 -> 上爻` - `yaoLines` item enum: `少阳 | 少阴 | 老阳 | 老阴` - Additional fields are forbidden. ### Frontend coin-face to `yaoLines` derivation rules This section is normative for frontend collection flows (`手动起卦` and `自动起卦`). - Both manual and auto flows MUST use the same canonical conversion logic. - Conversion baseline is manual flow semantics (`huaCount` baseline). - Auto flow (`ziCount` baseline) MUST be converted to `huaCount` before mapping. - Do not maintain separate mapping tables per page/screen. Canonical mapping (`huaCount` -> `yaoType`): - `0` -> `老阴` - `1` -> `少阳` - `2` -> `少阴` - `3` -> `老阳` Equivalent auto mapping (`ziCount` -> `yaoType`): - `0` -> `老阳` - `1` -> `少阴` - `2` -> `少阳` - `3` -> `老阴` Implementation requirement: - Frontend should centralize this conversion in one reusable converter and use it in both manual and auto screens. - `yaoLines` sent to backend MUST always be derived from this canonical mapping and keep order `初爻 -> 上爻`. ### `runtime_mode` rules - Allowed values: `chat | follow_up`. - `chat`: first run for a session. - `follow_up`: follow-up run in existing session. - Missing or invalid `runtime_mode` MUST return `422` with code `AGENT_RUNTIME_MODE_INVALID`. - Current backend behavior still requires `forwardedProps.divinationPayload` in both modes. ### Follow-up request note - Follow-up submit still uses `POST /api/v1/agent/runs`. - Required differences from first run: - `threadId` must be existing session id. - `forwardedProps.runtime_mode` must be `follow_up`. - `messages[0].content` is follow-up question text. - If `threadId` does not exist, backend returns `404` (`AGENT_SESSION_NOT_FOUND`). ## Event output contract During run streaming, backend emits standard AG-UI lifecycle events and two divination-relevant payload events: - Lifecycle: `RUN_STARTED`, `RUN_FINISHED` or `RUN_ERROR`. - Step events: `STEP_STARTED`, `STEP_FINISHED` with `stepName` (for example: `worker`). - Payload events: `DIVINATION_DERIVED`, `TEXT_MESSAGE_END`. ### 1) `DIVINATION_DERIVED` - Emitted once after backend derives hexagram data. - Payload field: `divination` (strict object). - Emitted only when `runtime_mode=chat`. `divination` object: ```json { "question": "我最近换工作是否合适?", "questionType": "事业", "divinationMethod": "手动起卦", "divinationTime": "2026年04月03日 20:30", "binaryCode": "101001", "changedBinaryCode": "100001", "guaName": "山火贲", "guaNameHant": "山火賁", "upperName": "艮", "lowerName": "离", "targetGuaName": "山雷颐", "targetGuaNameHant": "山雷頤", "worldPosition": 1, "responsePosition": 4, "hasChangingYao": true, "ganzhi": { "yearGanZhi": "丙午", "monthGanZhi": "壬辰", "dayGanZhi": "辛亥", "timeGanZhi": "乙巳", "yearKongWang": "子丑", "monthKongWang": "午未", "dayKongWang": "寅卯", "timeKongWang": "戌亥", "yueJian": "辰土", "riChen": "亥水", "yuePo": "戌土", "riChong": "巳火" }, "wuXingStatuses": { "木": "囚", "火": "休", "土": "旺", "金": "相", "水": "死" }, "yaoInfoList": [ { "position": 1, "spiritName": "虎", "spiritNameHant": "虎", "relationName": "官鬼", "relationNameHant": "官鬼", "tiganName": "卯", "elementName": "木", "isYang": true, "isChanging": false, "specialMark": "世" } ], "targetYaoInfoList": [], "fushenPositions": [2], "fushenInfoList": [ { "position": 2, "relationName": "父母", "relationNameHant": "父母", "tiganName": "午", "elementName": "火" } ], "specialStatus": [], "interactions": [], "timeEffect": [], "riChenZhangSheng": [] } ``` Field notes: - `guaNameHant`, `targetGuaNameHant`: Traditional Chinese variants for卦名. - `spiritNameHant`, `relationNameHant`: Traditional Chinese variants for六神/六亲 names. - `specialStatus`: Special hexagram status indicators. - `interactions`: 爻位 interaction descriptions. - `timeEffect`: Time-based effect descriptions. - `riChenZhangSheng`: 日辰长生相关 information. ### 2) `TEXT_MESSAGE_END` - Standard final answer event. - `runtime_mode=chat` fields: `status`, `sign_level`, `conclusion`, `focus_points`, `advice`, `keywords`, `answer`, `error`, `divination_derived`. - `runtime_mode=follow_up` fields: `status`, `answer`, `error`. - `runtime_mode=follow_up` MUST NOT include `sign_level`, `conclusion`, `focus_points`, `advice`, `keywords`, `divination_derived`. - Language rule: `conclusion`, `focus_points`, `advice`, `keywords`, `answer` should follow user `ai_language` preference unless user explicitly requests otherwise. - Canonical six-yao terms remain Chinese in protocol text (for example: 世爻、应爻、动爻、静爻、六亲、六神、伏神、月建、日辰、月破、日冲、空亡、五行旺衰). Frontend should combine: - structural divination data from `DIVINATION_DERIVED` - interpretation text from `TEXT_MESSAGE_END` ## History snapshot contract `GET /api/v1/agent/history` is the canonical replay source for frontend history list and result reconstruction. - When `threadId` is provided, backend returns full session messages ordered by `seq asc`. - When `threadId` is omitted, backend returns one latest assistant message per session for history list summary. - `threadId` is the session identifier (same value as AG-UI run/events path parameter). ### Required response shape ```json { "scope": "history_session_full", "threadId": "uuid|null", "day": null, "hasMore": false, "messages": [ { "id": "uuid", "threadId": "session-uuid", "seq": 12, "role": "assistant", "content": "...", "timestamp": "2026-04-05T12:34:56+00:00", "agent_output": { "status": "success", "sign_level": "中上签", "conclusion": ["..."], "focus_points": ["..."], "advice": ["..."], "keywords": ["..."], "answer": "...", "divination_derived": { "binaryCode": "101001", "changedBinaryCode": "100001", "guaName": "山火贲" } } }, { "id": "uuid", "threadId": "session-uuid", "seq": 11, "role": "user", "content": "我最近换工作是否合适?", "timestamp": "2026-04-05T12:34:12+00:00", "attachments": [ { "mimeType": "image/png", "url": "https://...signed..." } ] } ] } ``` Rules: - `scope=history_session_full` means full-thread replay with `messages` ordered by `seq`. - `scope=history_sessions_latest_assistant` means cross-session summary list. - Each `messages[i].threadId` is required and points to the owning session. Frontend must use this value to open detail replay and follow-up. - `day` and `hasMore` are retained for compatibility with old clients, but in `history_session_full` mode backend currently returns `day=null` and `hasMore=false`. - In `history_sessions_latest_assistant`, backend computes `hasMore` from `session_limit + 1` query. - `assistant` message MUST provide `agent_output` when backend has valid worker output metadata. - `agent_output.divination_derived` uses the same shape as `DIVINATION_DERIVED.divination` payload. - Frontend reconstructs divination result page from `agent_output` data, not from local mock data. - `agent_output.sign_level` allowed values: `上上签` / `中上签` / `中下签` / `下下签`. ## Migration note (`requires-migration`) ### Change set 1. `GET /agent/history?threadId=...` now returns full session replay (`scope=history_session_full`) instead of day-window snapshot semantics. 2. `messages[i].threadId` is now required. 3. `ui_schema` is removed from history payload. 4. `runtime_mode=follow_up` uses minimal `TEXT_MESSAGE_END` schema and no longer emits `DIVINATION_DERIVED`. 5. Added `DELETE /api/v1/agent/sessions/{threadId}` with idempotent `204 No Content` (already deleted or not found also returns 204). ### Frontend migration steps 1. Parse history as ordered message stream by `messages[].seq`. 2. Use `messages[].threadId` as session id for opening follow-up. 3. Do not depend on `ui_schema` in history payload. 4. Keep `day`/`hasMore` optional-read only; do not use them for pagination in thread replay mode. ### Rollback notes - If backend rolls back to day-window semantics, frontend must switch detail replay back to day-based load logic and stop requiring `messages[].threadId`. - Keep legacy parser branch behind feature flag during staged rollout. ## Error contract linkage - All errors use RFC7807 with extension `code` and optional `params`. - Error code registry source: `docs/protocols/common/http-error-codes.md`. ## Session delete contract ### `DELETE /api/v1/agent/sessions/{threadId}` - Authorization: current user must own the session. - Semantics: soft delete session (`deleted_at`), history reads filter deleted sessions by default. - Success: `204 No Content`. - Idempotent: already deleted or not found also returns `204 No Content`. ## Run cancel contract ### `POST /api/v1/agent/runs/{threadId}/cancel?runId=...` - Authorization: current user must own the session. - Request: `runId` query parameter required. - Success response: ```json { "threadId": "uuid", "runId": "run_xxx", "accepted": true } ``` Error codes (see common registry): - `AGENT_SESSION_NOT_FOUND` - `AGENT_FORBIDDEN` ## Attachment upload contract ### `POST /api/v1/agent/attachments` - Authorization: authenticated user. - Request: `multipart/form-data` with fields: - `threadId` (string, required) - `file` (binary, required) - Max file size: 5MB - Success response: ```json { "attachment": { "bucket": "agent-inputs", "path": "agent-inputs/{userId}/{filename}", "mimeType": "image/png", "url": "https://...signed..." } } ``` Error codes (see common registry): - `AGENT_ATTACHMENT_EMPTY` - `AGENT_ATTACHMENT_TOO_LARGE` - `AGENT_ATTACHMENT_STORAGE_UNAVAILABLE` - `AGENT_SESSION_NOT_FOUND` - `AGENT_FORBIDDEN` ## Attachment signed URL contract ### `GET /api/v1/agent/attachments/signed-url?bucket=...&path=...` - Authorization: authenticated user. - Query params: - `bucket` (string, required) - `path` (string, required) - Path must be under `agent-inputs/{userId}/` scope. - Success response: ```json { "bucket": "agent-inputs", "path": "agent-inputs/{userId}/{filename}", "url": "https://...signed..." } ``` Error codes (see common registry): - `AGENT_ATTACHMENT_BUCKET_INVALID` - `AGENT_ATTACHMENT_PATH_SCOPE_INVALID` - `AGENT_SIGNED_URL_GENERATION_FAILED` ## Transcribe contract ### `POST /api/v1/agent/transcribe` Request: - `multipart/form-data` - field name: `audio` - allowed content types: `audio/wav`, `audio/x-wav`, `audio/wave` - max payload bytes: `10MB` Response: ```json { "transcript": "我今天适合出门谈合作吗?" } ``` Error codes (see common registry): - `AGENT_AUDIO_UNSUPPORTED_FORMAT` - `AGENT_AUDIO_TOO_LARGE` - `AGENT_AUDIO_EMPTY` - `AGENT_ASR_UNAVAILABLE`