2026-04-03 19:04:46 +08:00
# 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
2026-04-08 17:23:02 +08:00
- Run/events contract: `backward-compatible` additive evolution.
- History contract (`GET /agent/history` ) currently `requires-migration` (see migration notes in this document).
2026-04-03 19:04:46 +08:00
- 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`
2026-04-10 16:45:45 +08:00
- Cancel run: `POST /api/v1/agent/runs/{threadId}/cancel?runId=...`
2026-04-03 19:04:46 +08:00
- Stream events: `GET /api/v1/agent/runs/{threadId}/events?runId=...`
2026-04-06 01:28:10 +08:00
- History snapshot: `GET /api/v1/agent/history`
2026-04-08 17:23:02 +08:00
- Delete session: `DELETE /api/v1/agent/sessions/{threadId}`
2026-04-10 16:45:45 +08:00
- Upload attachment: `POST /api/v1/agent/attachments`
- Get attachment signed URL: `GET /api/v1/agent/attachments/signed-url?bucket=...&path=...`
2026-04-08 17:23:02 +08:00
- Audio transcribe: `POST /api/v1/agent/transcribe`
2026-04-03 19:04:46 +08:00
## 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 `手动起卦 | 自动起卦`
2026-04-10 16:45:45 +08:00
- `questionType` : non-empty string, max 32 chars, recommended Chinese category labels
- `question` : non-empty string, max 300 chars
2026-04-03 19:04:46 +08:00
- `divinationTimeIso` : RFC3339 datetime with timezone offset
- `yaoLines` : exactly 6 items, order is `初爻 -> 上爻`
- `yaoLines` item enum: `少阳 | 少阴 | 老阳 | 老阴`
- Additional fields are forbidden.
2026-04-08 17:23:02 +08:00
### `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` ).
2026-04-03 19:04:46 +08:00
## Event output contract
During run streaming, backend emits standard AG-UI lifecycle events and two divination-relevant payload events:
2026-04-08 17:23:02 +08:00
- 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` .
2026-04-03 19:04:46 +08:00
### 1) `DIVINATION_DERIVED`
- Emitted once after backend derives hexagram data.
- Payload field: `divination` (strict object).
2026-04-08 17:23:02 +08:00
- Emitted only when `runtime_mode=chat` .
2026-04-03 19:04:46 +08:00
`divination` object:
``` json
{
"question" : "我最近换工作是否合适?" ,
"questionType" : "事业" ,
"divinationMethod" : "手动起卦" ,
"divinationTime" : "2026年04月03日 20:30" ,
"binaryCode" : "101001" ,
"changedBinaryCode" : "100001" ,
"guaName" : "山火贲" ,
"upperName" : "艮" ,
"lowerName" : "离" ,
"targetGuaName" : "山雷颐" ,
"worldPosition" : 1 ,
"responsePosition" : 4 ,
"hasChangingYao" : true ,
"ganzhi" : {
"yearGanZhi" : "丙午" ,
"monthGanZhi" : "壬辰" ,
"dayGanZhi" : "辛亥" ,
"timeGanZhi" : "乙巳" ,
"yearKongWang" : "子丑" ,
"monthKongWang" : "午未" ,
"dayKongWang" : "寅卯" ,
"timeKongWang" : "戌亥" ,
"yueJian" : "辰土" ,
"riChen" : "亥水" ,
"yuePo" : "戌土" ,
"riChong" : "巳火"
} ,
"wuXingStatuses" : {
"木" : "囚" ,
"火" : "休" ,
"土" : "旺" ,
"金" : "相" ,
"水" : "死"
} ,
"yaoInfoList" : [
{
"position" : 1 ,
"spiritName" : "虎" ,
"relationName" : "官鬼" ,
"tiganName" : "卯" ,
"elementName" : "木" ,
"isYang" : true ,
"isChanging" : false ,
"specialMark" : "世"
}
] ,
"targetYaoInfoList" : [ ] ,
"fushenPositions" : [ 2 ] ,
"fushenInfoList" : [
{
"position" : 2 ,
"relationName" : "父母" ,
"tiganName" : "午" ,
"elementName" : "火"
}
]
}
```
### 2) `TEXT_MESSAGE_END`
- Standard final answer event.
2026-04-08 17:23:02 +08:00
- `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` .
2026-04-07 18:43:34 +08:00
- 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: 世爻、应爻、动爻、静爻、六亲、六神、伏神、月建、日辰、月破、日冲、空亡、五行旺衰).
2026-04-03 19:04:46 +08:00
Frontend should combine:
- structural divination data from `DIVINATION_DERIVED`
- interpretation text from `TEXT_MESSAGE_END`
2026-04-06 01:28:10 +08:00
## History snapshot contract
`GET /api/v1/agent/history` is the canonical replay source for frontend history list and result reconstruction.
2026-04-08 17:23:02 +08:00
- 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).
2026-04-06 01:28:10 +08:00
### Required response shape
``` json
{
2026-04-08 17:23:02 +08:00
"scope" : "history_session_full" ,
2026-04-06 01:28:10 +08:00
"threadId" : "uuid|null" ,
2026-04-08 17:23:02 +08:00
"day" : null ,
2026-04-06 01:28:10 +08:00
"hasMore" : false ,
"messages" : [
{
"id" : "uuid" ,
2026-04-08 17:23:02 +08:00
"threadId" : "session-uuid" ,
2026-04-06 01:28:10 +08:00
"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" ,
2026-04-08 17:23:02 +08:00
"threadId" : "session-uuid" ,
2026-04-06 01:28:10 +08:00
"seq" : 11 ,
"role" : "user" ,
"content" : "我最近换工作是否合适?" ,
"timestamp" : "2026-04-05T12:34:12+00:00" ,
"attachments" : [
{
"mimeType" : "image/png" ,
"url" : "https://...signed..."
}
]
}
]
}
```
Rules:
2026-04-08 17:23:02 +08:00
- `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.
2026-04-06 01:28:10 +08:00
- `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: `上上签` / `中上签` / `中下签` / `下下签` .
2026-04-08 17:23:02 +08:00
## Migration note (`requires-migration`)
### Change set
2026-04-06 01:28:10 +08:00
2026-04-08 17:23:02 +08:00
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.
2026-04-06 01:28:10 +08:00
2026-04-03 19:04:46 +08:00
## Error contract linkage
- All errors use RFC7807 with extension `code` and optional `params` .
- Error code registry source: `docs/protocols/common/http-error-codes.md` .
2026-04-08 17:23:02 +08:00
## 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` .
2026-04-10 16:45:45 +08:00
## 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`
2026-04-08 17:23:02 +08:00
## 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`