- 修复 notifications 模块 datetime.now() 缺少时区问题 - 用 ApiProblemError 替换 BaseService 中的 HTTPException - 更新协议文档:添加错误码、繁体字段、邀请相关协议 - 升级 Docker 镜像版本
14 KiB
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-compatibleadditive evolution. - History contract (
GET /agent/history) currentlyrequires-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:
messages[0].content(question text)forwardedProps.divinationPayload(structured divination input)
Required request shape
{
"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 labelsquestion: non-empty string, max 300 charsdivinationTimeIso: RFC3339 datetime with timezone offsetyaoLines: exactly 6 items, order is初爻 -> 上爻yaoLinesitem 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 (
huaCountbaseline). - Auto flow (
ziCountbaseline) MUST be converted tohuaCountbefore 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.
yaoLinessent 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_modeMUST return422with codeAGENT_RUNTIME_MODE_INVALID. - Current backend behavior still requires
forwardedProps.divinationPayloadin both modes.
Follow-up request note
- Follow-up submit still uses
POST /api/v1/agent/runs. - Required differences from first run:
threadIdmust be existing session id.forwardedProps.runtime_modemust befollow_up.messages[0].contentis follow-up question text.- If
threadIddoes not exist, backend returns404(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_FINISHEDorRUN_ERROR. - Step events:
STEP_STARTED,STEP_FINISHEDwithstepName(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:
{
"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=chatfields:status,sign_level,conclusion,focus_points,advice,keywords,answer,error,divination_derived.runtime_mode=follow_upfields:status,answer,error.runtime_mode=follow_upMUST NOT includesign_level,conclusion,focus_points,advice,keywords,divination_derived.- Language rule:
conclusion,focus_points,advice,keywords,answershould follow userai_languagepreference 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
threadIdis provided, backend returns full session messages ordered byseq asc. - When
threadIdis omitted, backend returns one latest assistant message per session for history list summary. threadIdis the session identifier (same value as AG-UI run/events path parameter).
Required response shape
{
"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_fullmeans full-thread replay withmessagesordered byseq.scope=history_sessions_latest_assistantmeans cross-session summary list.- Each
messages[i].threadIdis required and points to the owning session. Frontend must use this value to open detail replay and follow-up. dayandhasMoreare retained for compatibility with old clients, but inhistory_session_fullmode backend currently returnsday=nullandhasMore=false.- In
history_sessions_latest_assistant, backend computeshasMorefromsession_limit + 1query. assistantmessage MUST provideagent_outputwhen backend has valid worker output metadata.agent_output.divination_deriveduses the same shape asDIVINATION_DERIVED.divinationpayload.- Frontend reconstructs divination result page from
agent_outputdata, not from local mock data. agent_output.sign_levelallowed values:上上签/中上签/中下签/下下签.
Migration note (requires-migration)
Change set
GET /agent/history?threadId=...now returns full session replay (scope=history_session_full) instead of day-window snapshot semantics.messages[i].threadIdis now required.ui_schemais removed from history payload.runtime_mode=follow_upuses minimalTEXT_MESSAGE_ENDschema and no longer emitsDIVINATION_DERIVED.- Added
DELETE /api/v1/agent/sessions/{threadId}with idempotent204 No Content(already deleted or not found also returns 204).
Frontend migration steps
- Parse history as ordered message stream by
messages[].seq. - Use
messages[].threadIdas session id for opening follow-up. - Do not depend on
ui_schemain history payload. - Keep
day/hasMoreoptional-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
codeand optionalparams. - 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:
runIdquery parameter required. - Success response:
{
"threadId": "uuid",
"runId": "run_xxx",
"accepted": true
}
Error codes (see common registry):
AGENT_SESSION_NOT_FOUNDAGENT_FORBIDDEN
Attachment upload contract
POST /api/v1/agent/attachments
- Authorization: authenticated user.
- Request:
multipart/form-datawith fields:threadId(string, required)file(binary, required)
- Max file size: 5MB
- Success response:
{
"attachment": {
"bucket": "agent-inputs",
"path": "agent-inputs/{userId}/{filename}",
"mimeType": "image/png",
"url": "https://...signed..."
}
}
Error codes (see common registry):
AGENT_ATTACHMENT_EMPTYAGENT_ATTACHMENT_TOO_LARGEAGENT_ATTACHMENT_STORAGE_UNAVAILABLEAGENT_SESSION_NOT_FOUNDAGENT_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:
{
"bucket": "agent-inputs",
"path": "agent-inputs/{userId}/{filename}",
"url": "https://...signed..."
}
Error codes (see common registry):
AGENT_ATTACHMENT_BUCKET_INVALIDAGENT_ATTACHMENT_PATH_SCOPE_INVALIDAGENT_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:
{
"transcript": "我今天适合出门谈合作吗?"
}
Error codes (see common registry):
AGENT_AUDIO_UNSUPPORTED_FORMATAGENT_AUDIO_TOO_LARGEAGENT_AUDIO_EMPTYAGENT_ASR_UNAVAILABLE